ClassCastException dans la boucle de Java
-
28-09-2019 - |
Question
Dans quelles circonstances peut ClassCastException se produire dans le code ci-dessous:
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);
}
}
}
Nous avons eu un cas similaire dans un environnement de production (mauvaise pratique, je sais) et le client a fourni un journal avec ClassCastException à la ligne avec le commentaire, mais je ne peux pas sembler le reproduire. Toute pensée?
Je sais que la machine virtuelle Java crée un itérateur en arrière-plan lors de l'utilisation foreach, mais peut-il créer une Iterator première dans certains cas et un dans d'autres cas paramétrisé?
Mise à jour : J'avais aussi un regard sur le bytecode généré et sous Windows, en utilisant JDK 1.6.0_21-b07 pas checkcast a été faite. Intéressant :)
Voici la principale méthode:
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
Merci pour toutes les réponses!
Mise à jour 2 : Je suis avec Eclipse IDE induits en erreur qui utilise son propre compilateur donc en fait le bytecode au-dessus est celui généré en utilisant le Eclipse compilateur . Regardez ici comment compiler manuellement le code avec Eclipse. En conclusion compilateur Eclipse génère différents byte-code du compilateur Sun dans certains cas, quelle que soit la plate-forme, le cas décrit ici étant l'un.
La solution
ne doit pas que le code toujours jeter un ClassCastException
? Il fait pour moi en utilisant le compilateur Sun Java 6 et exécution (sous Linux). Vous coulée Integer
s comme String
s. Le iterator créé sera un Iterator<String>
, mais il tente d'accéder au premier élément, qui est un Integer
, et il échoue.
Cela devient plus clair si vous changez votre tableau comme ceci:
return Arrays.asList("one", 2, 3);
Maintenant, la boucle fonctionne réellement pour le premier élément, parce que le premier élément est un String
et nous voyons la sortie; alors le Iterator<String>
échoue sur le second, parce qu'il est pas une chaîne.
Votre code fonctionne si vous suffit d'utiliser un List
générique au lieu d'un spécifique:
List list = getObjects();
for (Object o : list) {
System.out.println(o);
}
... ou, bien sûr, si vous utilisez List<Integer>
, puisque le contenu est Integer
s. Ce que vous faites déclenche maintenant un avertissement du compilateur - Note: Generics.java uses unchecked or unsafe operations.
-. Et pour cause
Cette modification fonctionne aussi:
for (Object o : (List)list)
... sans doute parce qu'à ce moment-là que vous avez affaire à un Iterator
, pas Iterator<String>
.
bozho a dit qu'il ne voit pas cette erreur sous Windows XP (n'a pas mentionné le compilateur et l'exécution, mais je devine Sun), et vous dites que vous n'êtes pas le voir (ou non fiable), donc clairement, il y a une certaine sensibilité à la mise en œuvre, mais la ligne de fond est la suivante: ne pas utiliser List<String>
pour interagir avec un List
de Integer
s. : -)
Voici le fichier que je suis la compilation:
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);
}
}
}
Voici la compilation:
tjc@forge:~/temp$ javac Generics.java Note: Generics.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
Voici la course:
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)
La ligne 12 est la déclaration de for
. Notez qu'il a sortie le premier élément, parce que je changé cela à un String
. Il n'a pas émis les autres. (Et avant que je fait ce changement, il n'a pas immédiatement.)
Voici le compilateur que je utilise:
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*
Voici le démontage, qui montre le 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 }
Encore une fois, cependant, la ligne de fond doit être: Ne pas utiliser un List<String>
pour interagir avec un List
qui contient des choses qui ne sont pas String
s. : -)
Autres conseils
Je ne peux pas reproduire non plus, mais je repérer les erreurs suivantes qui doivent être corrigées:
- retour
getObjects()
List<Integer>
, plutôt qu'un type cru - Ne vous attendez pas
List<String>
, mais au lieuList<Integer>
- Lorsque itérer
for (Integer o : list)
boucle
Le problème est la méthode static List getObjects() {
renvoie un générique (non paramétrée) List
. Et vous attribuez à List<String>
. Cette ligne doit donner un avertissement du compilateur, et qui aurait dû signaler un problème.
Cette partie:
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
sera simplifiée comme
List<String> list = getObjects();
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
Object o = iterator.next();
System.out.println(o);
}
Mais la mise en œuvre de Iterator va essayer de plâtre lors de l'appel de la méthode next()
l'envoi de contenu par le Iterator
dans un String
.
C'est la raison pour laquelle vous avez un CCE.
Solutions:
Soit l'utilisation des génériques partout ou ne les utilisent pas, mais il est vraiment important d'être cohérente. Si vous avez retourné un List<Integer>
ou List<? super Integer>
vous auriez vu ce problème au moment de la compilation.