ClassCastException in loop Java foreach
-
28-09-2019 - |
Domanda
In quali circostanze può ClassCastException verificarsi nel seguente codice:
import java.util.Arrays;
import java.util.List;
public class Generics {
static List getObjects() {
return Arrays.asList(1, 2, 3);
}
public static void main(String[] args) {
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
}
}
Abbiamo avuto un caso simile in un ambiente di produzione (cattiva pratica, lo so) e il cliente fornito un registro con ClassCastException in linea con il commento, ma io non riesco a riprodurlo. Ogni pensiero?
So che la JVM crea un iteratore in background quando si utilizza foreach, ma può creare un Iterator grezzo in alcuni casi e una parametrizzata in altri casi?
Aggiorna : Ho anche avuto uno sguardo al bytecode generato e su Windows, utilizzando JDK 1.6.0_21-b07 non checkcast è stato fatto. Interessante :)
Ecco il metodo principale:
public static void main(java.lang.String[]); Code: 0: invokestatic #34; //Method getObjects:()Ljava/util/List; 3: astore_1 4: aload_1 5: invokeinterface #36, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 10: astore_3 11: goto 28 14: aload_3 15: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 20: astore_2 21: getstatic #48; //Field java/lang/System.out:Ljava/io/PrintStream; 24: aload_2 25: invokevirtual #54; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 28: aload_3 29: invokeinterface #60, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 34: ifne 14 37: return
Grazie a tutti per le risposte!
Aggiorna 2 : Ho ottenuto indurre in errore con Eclipse IDE che utilizza il suo proprio compilatore quindi in realtà il bytecode sopra è quello generato utilizzando il Eclipse compilatore . Guardate qui come compilare manualmente il codice con Eclipse. In conclusione Eclipse compilatore genera byte-codice diverso dal compilatore Sun in alcuni casi, indipendentemente dalla piattaforma, il caso descritto qui essere uno.
Soluzione
non dovrebbero che il codice sempre gettare un ClassCastException
? Lo fa per me usando il compilatore Sun Java 6 e di esecuzione (su Linux). Stai colata Integer
s come String
s. L'iteratore creato sarà un Iterator<String>
, ma poi si tenta di accedere al primo elemento, che è un Integer
, e così non riesce.
Questo diventa più chiaro se si cambia l'array in questo modo:
return Arrays.asList("one", 2, 3);
Ora il ciclo effettivamente funziona per il primo elemento, perché il primo elemento è un String
e vediamo l'uscita; poi il Iterator<String>
non riesce sul secondo, perché non è una stringa.
Il tuo codice funziona solo se si utilizza un List
generica al posto di uno specifico:
List list = getObjects();
for (Object o : list) {
System.out.println(o);
}
... o, naturalmente, se si utilizza List<Integer>
, dal momento che i contenuti sono Integer
s. Quello che stai facendo ora innesca un compilatore avvertimento - Note: Generics.java uses unchecked or unsafe operations.
-. E per una buona ragione
Questa modifica funziona anche:
for (Object o : (List)list)
... presumibilmente perché a quel punto hai a che fare con una Iterator
, non un Iterator<String>
.
Bozho ha detto che non vede questo errore su Windows XP (non ha menzionato quale compilatore e runtime, ma sto cercando di indovinare Sun), e dire che non stiamo vedendo (o non affidabile), in modo da chiaramente c'è una certa sensibilità realizzazione qui, ma la linea di fondo è: non utilizzare List<String>
di interagire con una List
di Integer
s. : -)
Ecco il file Sto compilando:
import java.util.Arrays;
import java.util.List;
public class Generics {
static List getObjects() {
return Arrays.asList("one", 2, 3);
}
public static void main(String[] args) {
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
}
}
Ecco la compilazione:
tjc@forge:~/temp$ javac Generics.java Note: Generics.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
Ecco il percorso:
tjc@forge:~/temp$ java Generics one Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at Generics.main(Generics.java:12)
Linea 12 è la dichiarazione for
. Si noti che lo ha fatto in uscita il primo elemento, perché ho cambiato che a un String
. Essa non ha uscita gli altri. (E prima ho fatto che il cambiamento, non è riuscito immediatamente.)
Ecco il compilatore che sto usando:
tjc@forge:~/temp$ which javac /usr/bin/javac tjc@forge:~/temp$ ll /usr/bin/javac lrwxrwxrwx 1 root root 23 2010-09-30 16:37 /usr/bin/javac -> /etc/alternatives/javac* tjc@forge:~/temp$ ll /etc/alternatives/javac lrwxrwxrwx 1 root root 33 2010-09-30 16:37 /etc/alternatives/javac -> /usr/lib/jvm/java-6-sun/bin/javac*
Ecco il disassemblaggio, che mostra la checkcast
:
tjc@forge:~/temp$ javap -c Generics Compiled from "Generics.java" public class Generics extends java.lang.Object{ public Generics(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return static java.util.List getObjects(); Code: 0: iconst_3 1: anewarray #2; //class java/io/Serializable 4: dup 5: iconst_0 6: ldc #3; //String one 8: aastore 9: dup 10: iconst_1 11: iconst_2 12: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 15: aastore 16: dup 17: iconst_2 18: iconst_3 19: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 22: aastore 23: invokestatic #5; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 26: areturn public static void main(java.lang.String[]); Code: 0: invokestatic #6; //Method getObjects:()Ljava/util/List; 3: astore_1 4: aload_1 5: invokeinterface #7, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 10: astore_2 11: aload_2 12: invokeinterface #8, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 17: ifeq 40 20: aload_2 21: invokeinterface #9, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 26: checkcast #10; //class java/lang/String 29: astore_3 30: getstatic #11; //Field java/lang/System.out:Ljava/io/PrintStream; 33: aload_3 34: invokevirtual #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 37: goto 11 40: return }
Anche in questo caso, però, la linea di fondo deve essere: non utilizzare un List<String>
di interagire con una List
che contiene cose che non sono String
s. : -)
Altri suggerimenti
Non riesco a riprodurre sia, ma ho posto i seguenti errori che devono essere corretti:
-
getObjects()
makeList<Integer>
ritorno, piuttosto che un tipo grezzo - Non aspettatevi
List<String>
, ma unList<Integer>
invece - Quando l'iterazione, ciclo
for (Integer o : list)
Il problema è il metodo static List getObjects() {
restituisce un generico (non parametrizzato) List
. E si assegna a List<String>
. Questa linea dovrebbe dare un avviso del compilatore, e che avrebbe dovuto segnalato un problema.
Questa parte:
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
sarà semplificata come
List<String> list = getObjects();
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
Object o = iterator.next();
System.out.println(o);
}
Ma l'attuazione di Iterator cercherà di getto quando si chiama il metodo next()
l'invio di contenuti dal Iterator
in un String
.
È per questo che hanno una CCE.
Soluzioni:
In entrambi i generici uso in tutto il mondo o non li usano, ma è davvero importante essere coerente. Se tu avessi restituito un List<Integer>
o un List<? super Integer>
avreste visto questo problema in fase di compilazione.