ClassCastException en bucle foreach Java
-
28-09-2019 - |
Pregunta
¿En qué circunstancias puede ocurrir ClassCastException en el código siguiente:
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);
}
}
}
Hemos tenido un caso similar en un entorno de producción (mala práctica, lo sé) y el cliente proporciona un registro con ClassCastException en la línea con el comentario, pero me parece que no puede reproducirlo. ¿Alguna idea?
Sé que la JVM crea un iterador en el fondo cuando se utiliza foreach, pero puedo crear un iterador que prima en algunos casos y una parametrizada en otros casos?
Actualizar También tuve un vistazo al código de bytes generado y en Windows, usando JDK 1.6.0_21-b07 sin checkcast fue hecho. Interesante :)
Este es el método principal:
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
Gracias a todos por las respuestas!
Actualización 2 : Tengo engañar con Eclipse IDE que utiliza su propio compilador lo que en realidad el código de bytes por encima de ella es la generada mediante el Eclipse compilador . Mira aquí cómo compilar código manualmente con Eclipse. En conclusión Eclipse compilador genera el código de bytes diferente del compilador de Sun en algunos casos, independientemente de la plataforma, el caso descrito aquí ser uno.
Solución
En caso de que no código siempre lanzar una ClassCastException
? Se hace por mí usando el compilador de Sun Java 6 y tiempo de ejecución (en Linux). Estás fundición Integer
s como String
s. El iterador creada será una Iterator<String>
, pero entonces se trata de acceder al primer elemento, que es un Integer
, y así se produce un error.
Esto queda más claro si cambia la matriz de esta manera:
return Arrays.asList("one", 2, 3);
Ahora el bucle realmente funciona para el primer elemento, debido a que el primer elemento es un String
y vemos la salida; entonces el Iterator<String>
produce un error en el segundo, porque no es una cadena.
Su código funciona si sólo se utiliza un List
genérico en lugar de uno específico:
List list = getObjects();
for (Object o : list) {
System.out.println(o);
}
... o, por supuesto, si se utiliza List<Integer>
, ya que los contenidos son Integer
s. Lo que está haciendo ahora desencadena una advertencia del compilador - Note: Generics.java uses unchecked or unsafe operations.
-. Y por una buena razón
Esta modificación también funciona:
for (Object o : (List)list)
... presumiblemente porque en ese punto que está tratando con un Iterator
, no un Iterator<String>
.
bozho ha dicho que no ve este error en Windows XP (no mencionó los cuales compilador y tiempo de ejecución, pero supongo Sun), y usted dice que usted no está viendo (o no fiable), por lo claramente hay algo de sensibilidad aplicación aquí, pero la conclusión es: no utilice List<String>
para interactuar con un List
de Integer
s. : -)
Éste es el fichero Estoy 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);
}
}
}
Aquí está la compilación:
tjc@forge:~/temp$ javac Generics.java Note: Generics.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
Aquí está la carrera:
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 línea 12 es la declaración for
. Tenga en cuenta que lo hizo el primer elemento de salida, porque he cambiado eso a un String
. No fue así dar salida a los otros. (Y antes de que hiciera ese cambio, no logró inmediatamente.)
Este es el compilador que estoy 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*
Aquí está el desmontaje, que muestra 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 }
Una vez más, sin embargo, el resultado final tiene que ser: no utilice un List<String>
para interactuar con un List
que contiene cosas que no son String
s. : -)
Otros consejos
No se puede reproducir tampoco, pero me detectar los siguientes errores que deben corregirse:
-
getObjects()
devolverloList<Integer>
, en lugar de un tipo de prima - No hay que esperar
List<String>
, pero una vezList<Integer>
- Cuando la iteración,
for (Integer o : list)
bucle
El problema es la static List getObjects() {
método devuelve un List
genérico (no parametrizado). Y lo ha asignado a List<String>
. Esta línea debe dar una advertencia del compilador, y que debería haber señalado un problema.
Esta parte:
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
¿Se puede simplificar como
List<String> list = getObjects();
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
Object o = iterator.next();
System.out.println(o);
}
Sin embargo, la implementación de iterador intentará fundido al llamar al método next()
el envío por el contenido de Iterator
en un String
.
Es por eso que tienen un CCE.
Soluciones:
genéricos O bien utilizar todas partes o no los usa, pero es muy importante ser constante. Si había vuelto un List<Integer>
o una List<? super Integer>
que habría visto este problema en tiempo de compilación.