Mam coś List<SubClass>
, co chcę traktować jako List<BaseClass>
. Wygląda na to, że nie powinno to stanowić problemu, ponieważ rzutowanie a SubClass
na a BaseClass
jest bardzo proste, ale mój kompilator narzeka, że rzutowanie jest niemożliwe.
Jaki jest więc najlepszy sposób na uzyskanie odniesienia do tych samych obiektów, co List<BaseClass>
?
W tej chwili tworzę nową listę i kopiuję starą listę:
List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)
Ale jak rozumiem, musi to stworzyć zupełnie nową listę. Jeśli to możliwe, chciałbym nawiązać do pierwotnej listy!
java
inheritance
casting
Riley Lark
źródło
źródło
Odpowiedzi:
W składni tego rodzaju przypisania używany jest symbol wieloznaczny:
Ważne jest, aby zdać sobie sprawę, że
List<SubClass>
to nie zamiennie zList<BaseClass>
. Kod, który zachowuje odniesienie do,List<SubClass>
będzie oczekiwał, że każda pozycja na liście będzieSubClass
. Jeśli inna część kodu odnosi się do listy jako aList<BaseClass>
, kompilator nie będzie narzekać, gdy zostanie wstawiony aBaseClass
lubAnotherSubClass
. Ale spowoduje to znakClassCastException
dla pierwszego fragmentu kodu, który zakłada, że wszystko na liście toSubClass
.Kolekcje ogólne nie zachowują się tak samo jak tablice w języku Java. Tablice są kowariantne; to znaczy, można to zrobić:
Jest to dozwolone, ponieważ tablica „zna” typ swoich elementów. Jeśli ktoś spróbuje zapisać
SubClass
w tablicy coś, co nie jest instancją (poprzezbases
odniesienie), zostanie zgłoszony wyjątek czasu wykonywania.Kolekcje ogólne nie „znają” swojego typu składnika; ta informacja jest „usuwana” w czasie kompilacji. Dlatego nie mogą zgłosić wyjątku czasu wykonywania, gdy wystąpi nieprawidłowy magazyn. Zamiast tego
ClassCastException
zostanie podniesiony w jakimś odległym, trudnym do skojarzenia punkcie w kodzie, gdy wartość jest odczytywana z kolekcji. Jeśli zważasz na ostrzeżenia kompilatora dotyczące bezpieczeństwa typów, unikniesz tego typu błędów w czasie wykonywania.źródło
Erickson już wyjaśnił, dlaczego nie możesz tego zrobić, ale oto kilka rozwiązań:
Jeśli chcesz tylko usunąć elementy z listy bazowej, w zasadzie twoja metoda odbioru powinna być zadeklarowana jako przyjmująca
List<? extends BaseClass>
.Ale jeśli tak nie jest i nie możesz tego zmienić, możesz zawinąć listę
Collections.unmodifiableList(...)
, co pozwala na zwrócenie listy nadtypu parametru argumentu. (Pozwala to uniknąć problemu z bezpieczeństwem typów, zgłaszając wyjątek UnsupportedOperationException podczas prób wstawienia).źródło
Jak wyjaśnił @erickson, jeśli naprawdę chcesz odwołać się do oryginalnej listy, upewnij się, że żaden kod nie wstawia niczego do tej listy, jeśli kiedykolwiek zechcesz użyć jej ponownie w pierwotnej deklaracji. Najprostszym sposobem na uzyskanie tego jest po prostu przerzucenie go na zwykłą, starą nieogólną listę:
List<BaseClass> baseList = (List)new ArrayList<SubClass>();
Nie polecałbym tego, jeśli nie wiesz, co dzieje się z Listą i sugerowałbym zmianę dowolnego kodu, który wymaga Listy, aby zaakceptować listę, którą masz.
źródło
Poniżej znajduje się przydatny fragment, który działa. Tworzy nową listę tablic, ale tworzenie obiektów JVM nad głową jest nieistotne.
Widziałem, że inne odpowiedzi są niekoniecznie skomplikowane.
List<BaseClass> baselist = new ArrayList<>(sublist);
źródło
Brakowało mi odpowiedzi, w której po prostu przesłałeś oryginalną listę, używając podwójnej obsady. Więc tutaj jest dla kompletności:
Nic nie jest kopiowane, a operacja jest szybka. Jednak oszukujesz tutaj kompilator, więc musisz absolutnie upewnić się, że nie modyfikujesz listy w taki sposób, że
subList
zaczyna się zawierać elementy innego podtypu. Gdy mamy do czynienia z niezmiennymi listami, zwykle nie stanowi to problemu.źródło
List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)
źródło
To, co próbujesz zrobić, jest bardzo przydatne i uważam, że muszę to robić bardzo często w kodzie, który piszę. Przykład użycia:
Powiedzmy, że mamy interfejs
Foo
i mamyzorking
pakiet,ZorkingFooManager
który tworzy i zarządza wystąpieniami pakietu privateZorkingFoo implements Foo
. (Bardzo powszechny scenariusz.)Więc
ZorkingFooManager
musi zawierać,private Collection<ZorkingFoo> zorkingFoos
ale musi ujawniaćpublic Collection<Foo> getAllFoos()
.Większość programistów Java nie zastanowiłaby się dwa razy, zanim zaimplementowałaby
getAllFoos()
alokację nowegoArrayList<Foo>
, zapełniając go wszystkimi elementami zzorkingFoos
i zwracając. Cieszy mnie myśl, że około 30% wszystkich cykli zegara zużywanych przez kod Java działający na milionach maszyn na całym świecie nie robi nic poza tworzeniem bezużytecznych kopii ArrayLists, które są zbierane w mikrosekundach po ich utworzeniu.Rozwiązaniem tego problemu jest oczywiście zdegradowanie kolekcji. Oto najlepszy sposób, aby to zrobić:
static <T,U extends T> List<T> downCastList( List<U> list ) { return castList( list ); }
Co prowadzi nas do
castList()
funkcji:static <T,E> List<T> castList( List<E> list ) { @SuppressWarnings( "unchecked" ) List<T> result = (List<T>)list; return result; }
result
Zmienna pośrednia jest konieczna ze względu na wypaczenie języka Java:return (List<T>)list;
tworzy wyjątek „niezaznaczone rzutowanie”; na razie w porządku; ale wtedy:@SuppressWarnings( "unchecked" ) return (List<T>)list;
jest nielegalnym użyciem adnotacji z ostrzeżeniami.Tak więc, nawet jeśli użycie
@SuppressWarnings
wreturn
instrukcji nie jest koszerne , najwyraźniej można go używać w przypisaniu, więc dodatkowa zmienna „result” rozwiązuje ten problem. (W każdym razie powinien być zoptymalizowany przez kompilator lub przez JIT).źródło
Co powiesz na rzucanie wszystkich elementów. Utworzy nową listę, ale odniesie się do oryginalnych obiektów ze starej listy.
źródło
To jest kompletny działający fragment kodu używający Generics do rzutowania listy podklas do superklasy.
Metoda wywołująca, która przekazuje typ podklasy
List<SubClass> subClassParam = new ArrayList<>(); getElementDefinitionStatuses(subClassParam);
Callee, która akceptuje dowolny podtyp klasy bazowej
private static List<String> getElementDefinitionStatuses(List<? extends BaseClass> baseClassVariableName) { return allElementStatuses; } }
źródło
Coś takiego też powinno działać:
public static <T> List<T> convertListWithExtendableClasses( final List< ? extends T> originalList, final Class<T> clazz ) { final List<T> newList = new ArrayList<>(); for ( final T item : originalList ) { newList.add( item ); }// for return newList; }
Naprawdę nie wiem, dlaczego Clazz jest potrzebny w Eclipse.
źródło