Najbardziej efektywny sposób rzutowania List <SubClass> na List <BaseClass>

139

Mam coś List<SubClass>, co chcę traktować jako List<BaseClass>. Wygląda na to, że nie powinno to stanowić problemu, ponieważ rzutowanie a SubClassna a BaseClassjest 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!

Riley Lark
źródło
3
odpowiedź, którą masz tutaj: stackoverflow.com/questions/662508/…
lukastymo

Odpowiedzi:

179

W składni tego rodzaju przypisania używany jest symbol wieloznaczny:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

Ważne jest, aby zdać sobie sprawę, że List<SubClass>to nie zamiennie z List<BaseClass>. Kod, który zachowuje odniesienie do, List<SubClass>będzie oczekiwał, że każda pozycja na liście będzie SubClass. Jeśli inna część kodu odnosi się do listy jako a List<BaseClass>, kompilator nie będzie narzekać, gdy zostanie wstawiony a BaseClasslub AnotherSubClass. Ale spowoduje to znak ClassCastExceptiondla pierwszego fragmentu kodu, który zakłada, że ​​wszystko na liście to SubClass.

Kolekcje ogólne nie zachowują się tak samo jak tablice w języku Java. Tablice są kowariantne; to znaczy, można to zrobić:

SubClass[] subs = ...;
BaseClass[] bases = subs;

Jest to dozwolone, ponieważ tablica „zna” typ swoich elementów. Jeśli ktoś spróbuje zapisać SubClassw tablicy coś, co nie jest instancją (poprzez basesodniesienie), 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 ClassCastExceptionzostanie 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.

erickson
źródło
Należy zauważyć, że w przypadku Arrays zamiast ClassCastException podczas pobierania obiektu, który nie jest typu SubClass (lub pochodnej), podczas wstawiania zostanie wyświetlony wyjątek ArrayStoreException.
Axel
Szczególnie dziękuję za wyjaśnienie, dlaczego tych dwóch list nie można uznać za takie same.
Riley Lark
System typów Javy udaje, że tablice są kowariantne, ale tak naprawdę nie można ich zastąpić, co potwierdza ArrayStoreException.
Paŭlo Ebermann
39

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).

Paŭlo Ebermann
źródło
Świetny! nie znał tego.
keuleJ
Dziękuję Ci bardzo!
Woland
14

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.

hd42
źródło
3

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);
sigirisetti
źródło
1
Dziękujemy za ten fragment kodu, który może zapewnić natychmiastową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego wartość edukacyjną, pokazując, dlaczego jest to dobre rozwiązanie problemu i uczyniłoby je bardziej użytecznym dla przyszłych czytelników z podobnymi, ale nie identycznymi pytaniami. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, i dać wskazówkę co zastosować ograniczenia i założenia. W szczególności, czym różni się to od kodu w pytaniu, które tworzy nową listę?
Toby Speight,
Narzut nie jest bez znaczenia, ponieważ musi również (płytko) skopiować każde odniesienie. W związku z tym skaluje się wraz z rozmiarem listy, więc jest operacją O (n).
john16384
2

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:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

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 subListzaczyna się zawierać elementy innego podtypu. Gdy mamy do czynienia z niezmiennymi listami, zwykle nie stanowi to problemu.

john16384
źródło
1

List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)

jtahlborn
źródło
5
To nie powinno działać, ponieważ dla tej metody wszystkie argumenty i wynik przyjmują ten sam parametr typu.
Paŭlo Ebermann
dziwne, masz rację. z jakiegoś powodu odniosłem wrażenie, że ta metoda jest przydatna do rozpowszechniania ogólnej kolekcji. nadal może być używany w ten sposób i zapewni "bezpieczną" kolekcję, jeśli chcesz użyć suppresswarnings.
jtahlborn
1

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 Fooi mamy zorkingpakiet, ZorkingFooManagerktóry tworzy i zarządza wystąpieniami pakietu private ZorkingFoo implements Foo. (Bardzo powszechny scenariusz.)

Więc ZorkingFooManagermusi zawierać, private Collection<ZorkingFoo> zorkingFoosale musi ujawniać public Collection<Foo> getAllFoos().

Większość programistów Java nie zastanowiłaby się dwa razy, zanim zaimplementowałaby getAllFoos()alokację nowego ArrayList<Foo>, zapełniając go wszystkimi elementami z zorkingFoosi 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;
}

resultZmienna 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 @SuppressWarningsw returninstrukcji 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).

Mike Nakis
źródło
1

Co powiesz na rzucanie wszystkich elementów. Utworzy nową listę, ale odniesie się do oryginalnych obiektów ze starej listy.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
drordk
źródło
To jest cały fragment kodu, który działa w celu rzutowania listy podklas do superklasy.
kanaparthikiran
0

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;
    }
}
kanaparthikiran
źródło
-1

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.

olivervbk
źródło