java: (String []) List.toArray () zwraca wyjątek ClassCastException

107

Poniższy kod (uruchamiany w systemie Android) zawsze daje mi wyjątek ClassCastException w trzeciej linii:

final String[] v1 = i18nCategory.translation.get(id);
final ArrayList<String> v2 = new ArrayList<String>(Arrays.asList(v1));
String[] v3 = (String[])v2.toArray();

Dzieje się tak również, gdy v2 to Object [0], a także gdy są w nim ciągi znaków. Każdy pomysł, dlaczego?

Gavriel
źródło
Możesz przeczytać o kowariancji i kontrawariancji - en.wikipedia.org/wiki/…
Hut8
A co z przypadkiem, w którym T jest interfejsem z fabryczną metodą tworzenia instancji.
sebaj,
2
@LaceCard - jest to tylko bardzo pośrednio związane z kowariancją / kontrawariancją. Prawdziwym problemem jest to, że jest to bezpośrednia konsekwencja określonego zachowania toArray()metody.
Stephen C

Odpowiedzi:

250

Dzieje się tak, ponieważ kiedy używasz

 toArray() 

zwraca Object [], którego nie można rzutować na String [] (nawet jeśli zawartość jest ciągami znaków) Dzieje się tak, ponieważ metoda toArray pobiera tylko

List 

i nie

List<String>

ponieważ typy generyczne to tylko kod źródłowy i nie są dostępne w czasie wykonywania, więc nie może określić, jaki typ tablicy należy utworzyć.

posługiwać się

toArray(new String[v2.size()]);

który przydziela odpowiedni rodzaj tablicy (String [] i odpowiedni rozmiar)

MeBigFatGuy
źródło
3
bo generics, to dlaczego
Kaleb Brasee
2
@Kaleb - nieprawda. A przynajmniej nie jest to pierwotny powód. Te toArraymetody istniały przed klasach zbiórki były średnie.
Stephen C
10
To jest poprawne rozwiązanie, ale nie właściwe wyjaśnienie. Nie możesz przesłać Object [] do Double [], ponieważ jest to funkcja języka, nic więcej. Nie ma to nic wspólnego z generykami. Możesz rzucić Object na Double, zakładając, że jest to naprawdę Double. Logicznie rzecz biorąc, możesz zrobić to samo z tablicą, ale po prostu nie jest to część języka. Jeśli rzucisz Object na Double, nastąpi sprawdzenie w czasie wykonywania, aby upewnić się, że Object faktycznie JEST Double. W przypadku rzutowania Double na Object nie ma sprawdzenia w czasie wykonywania, ponieważ Object znajduje się w hierarchii dziedziczenia Double.
KyleM
5
Robiąc to w IntelliJ, dostaję ostrzeżenie, aby użyć toArray(new String[0])raczej: „W starszych wersjach Javy zalecano używanie wstępnie ustalonej tablicy, ponieważ wywołanie odbicia, które jest konieczne do utworzenia tablicy o odpowiednim rozmiarze, było dość powolne. Jednak od późnych aktualizacji OpenJDK 6 to wywołanie było zintegro- wane, sprawiając, że wydajność wersji pustej tablicy była taka sama, a czasem nawet lepsza. Również przekazywanie tablicy o wstępnie ustalonym rozmiarze jest niebezpieczne dla kolekcji współbieżnej, ponieważ między wywołaniem sizea jest możliwy wyścig toArray. "
Mikepote
35

Używasz złego toArray()

Pamiętaj, że leki generyczne w Javie to głównie cukier syntaktyczny. ArrayList w rzeczywistości nie wie, że wszystkie jej elementy są łańcuchami.

Aby rozwiązać problem, zadzwoń toArray(T[]). W Twoim przypadku,

String[] v3 = v2.toArray(new String[v2.size()]);

Zwróć uwagę, że uogólniony formularz toArray(T[])zwraca T[], więc wynik nie musi być jawnie rzutowany.

Dilum Ranatunga
źródło
1
String[] v3 = v2.toArray(new String[0]); 

również załatwia sprawę, pamiętaj, że nie musisz już nawet rzucać, gdy odpowiedni typ ArrayType zostanie przekazany metodzie.

Mam nadzieję, że pomoże
źródło
0
String[] str = new String[list.size()];
str = (String[]) list.toArray(str);

Użyj w ten sposób.

MakNe
źródło
1
Proszę! Sformatuj swój kod! Zaznacz wszystko, naciskając "ctrl + k", lub dodaj "` "przed pierwszą literą kodu i na ostatniej kolejnej. Możesz także wybrać „{}” w pomocy u góry podczas pisania odpowiedzi!
MK