ClassCastException in Java foreach loop
Asked Answered
B

4

6

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 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.

Berserk answered 7/10, 2010 at 11:6 Comment(5)
Java generics are biting you! ;-)Dionedionis
Tips: (1) Use all the info at your disposal - the exception should tell you which classes were involved, e.g. java.lang.ClassCastException: XXXX cannot be cast to YYYY. (2) The line number sometimes off by a few lines, so look a few lines above and below the reported line for possible sources of the exception.Metaplasia
indeed, give the exact message.Hocus
The code above is a mimic of the production code but the "translated" error was ClassCastException Integer cannot be cast to StringBerserk
Thanks for posting your disassembly. That's fascinating about the checkcast being in some compiled versions (mine, Tim's when he used 1.6.0_21) and not others (bozho's, yours)...Marengo
M
5

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 Integers as Strings. 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 Integers. 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 Integers. :-)

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 Strings. :-)

Marengo answered 7/10, 2010 at 11:12 Comment(14)
the String part is lost at runtime (erasure), so no conversion actually takes place. But it is very wrong to do it the way he did.Hocus
@Bozho: Clearly not in the Iterator<E> implementation. When I compile his code with the Sun Java 6 compiler (1.6.0_20) on Linux and run it, I reliably get the ClassCastException.Marengo
I ran it and it does not throw an exception :), 1.6.0_20, Windows XPHocus
Well it doesn't throw a ClassCastException because I'm using Object to iterate over the list.Berserk
@robert: But you're doing that with a Iterator<String>.Marengo
@Bozho: Don't know what to tell you. Sun's Java 6 compiler and runtime, on Linux, throw the exception.Marengo
It isn't throwing an Exception on Linux 1.6.0_20 neither. It only throws one when you try to alter the type in the for control from Object to String for example.Calhoun
@Octavian: "It isn't throwing an Exception on Linux 1.6.0_20 neither." Yes, it is, for me. If it isn't for you, I wonder what's different?Marengo
@Bozho: Hmm...Compiling on 1.6.0_21 (for me) inserts a checkcast <java/lang/String> instruction, which causes the exception.Prehistoric
@Octavian What compiler are you using?Berserk
@ T.J. Crowder: I have no clue what the difference is. I can run it without any exceptions for as many times as I try.Calhoun
This is a rather interesting discussion, but I think we all agree what is wrong with the code and what has to be fixed :)Hocus
@Octavian: Yes, but is it Sun's compiler? Or the OpenJDK?Marengo
@Bozho: "...but I think we all agree what is wrong with the code and what has to be fixed" Completely. :-)Marengo
H
1

I can't reproduce it either, but I spot the following mistakes that must be corrected:

  • make getObjects() return List<Integer>, rather than a raw type
  • Don't expect List<String>, but a List<Integer> instead
  • When iterating, loop for (Integer o : list)
Hocus answered 7/10, 2010 at 11:14 Comment(0)
B
0

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.

Bainite answered 7/10, 2010 at 11:15 Comment(0)
B
0

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.

Britishism answered 7/10, 2010 at 11:20 Comment(1)
I think you meant Iterator<String>, not Iterator<List>.Marengo

© 2022 - 2024 — McMap. All rights reserved.