ClassCastException in Java foreach loop
In what circumstances can ClassCastException occur in the code below:
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);
}
}
}
We had a similar case in a production environment (bad practice, I know) and the customer provided a log with ClassCastException at the line with the comment but I can't seem to reproduce it. Any thoughts?
I know that the JVM creates an iterator in the background when using foreach, but can it create a raw Iterator in some cases and a parametrized one in other cases?
Update:I also had a look at the bytecode generated and on Windows, using JDK 1.6.0_21-b07 no checkcast was made. Interesting :)
Here is the main method:
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
Thanks all for the answers!
Update 2: I got mislead with Eclipse IDE that uses its own compiler so actually the bytecode above it's the one generated using the Eclipse compiler. Look here how 开发者_JS百科to manually compile code with Eclipse. In conclusion Eclipse compiler generates different byte-code from the Sun compiler in some cases, regardless of the platform, the case described here being one.
Shouldn't that code always throw a ClassCastException
? It does for me using the Sun Java 6 compiler and runtime (on Linux). You're casting Integer
s as String
s. The iterator created will be an Iterator<String>
, but then it tries to access the first element, which is an Integer
, and so it fails.
This gets clearer if you change your array like so:
return Arrays.asList("one", 2, 3);
Now the loop actually works for the first element, because the first element is a String
and we see the output; then the Iterator<String>
fails on the second one, because it isn't a string.
Your code works if you just use a generic List
instead of a specific one:
List list = getObjects();
for (Object o : list) {
System.out.println(o);
}
...or, of course, if you use List<Integer>
, since the contents are Integer
s. What you're doing now triggers a compiler warning — Note: Generics.java uses unchecked or unsafe operations.
— and for good reason.
This modification also works:
for (Object o : (List)list)
...presumably because at that point you're dealing with an Iterator
, not an Iterator<String>
.
bozho has said he doesn't see this error on Windows XP (didn't mention which compiler and runtime, but I'm guessing Sun's), and you say you're not seeing it (or not reliably), so clearly there's some implementation sensitivity here, but the bottom line is: Don't use List<String>
to interact with a List
of Integer
s. :-)
Here's the file I'm compiling:
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);
}
}
}
Here's the compilation:
tjc@forge:~/temp$ javac Generics.java Note: Generics.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
Here's the run:
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)
Line 12 is the for
statement. Note that it did output the first element, because I changed that to a String
. It didn't output the others. (And before I made that change, it failed immediately.)
Here's the compiler I'm using:
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*
Here's the disassembly, which shows the 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 }
Again, though, the bottom line has to be: Don't use a List<String>
to interact with a List
that contains things that aren't String
s. :-)
I can't reproduce it either, but I spot the following mistakes that must be corrected:
- make
getObjects()
returnList<Integer>
, rather than a raw type - Don't expect
List<String>
, but aList<Integer>
instead - When iterating, loop
for (Integer o : list)
The problem is the method static List getObjects() {
returns a generic (non parameterised) List
. And you assign it to List<String>
. This line should give a compiler warning, and that should have signalled a problem.
This part :
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
Will be simplified as
List<String> list = getObjects();
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
Object o = iterator.next();
System.out.println(o);
}
But the implementation of Iterator will try to cast when calling the next()
method the content send by the Iterator
in a String
.
That's why you have a CCE.
Solutions :
Either use generics everywhere or don't use them, but it's really important to be consistant. If you had returned a List<Integer>
or a List<? super Integer>
you would have seen this problem at compilation time.
精彩评论