1

Could anybode exlain why generic <Object[]> causes a ClassCastException (RuntimeException!)

I know that all generics removing while compilation phase and do not have any effect to the bytecode. But it seems it has some nuance.

Here my example (simplified for this post):

public class CastTest {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(new Object());
        List<Object[]> b = a;
        b.get(0).toString();
    }
}

this code returns:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to [Ljava.lang.Object;
    at CastTest.main(CastTest.java:9)

I do not understand what is wrong with this code. Could anybody explain this behavior?

5
  • 5
    Because a contains Object, and not Object[]. Commented Apr 8, 2015 at 8:06
  • It is not mater, I have not used any casts in this example Commented Apr 8, 2015 at 8:11
  • It's implicitly done. Commented Apr 8, 2015 at 8:17
  • Could you explain how it possible? Because type erasure (docs.oracle.com/javase/tutorial/java/generics/erasure.html) do not allow generics do something in a runtime Commented Apr 8, 2015 at 8:19
  • 1
    Because Object[] instanceof Object is true, no reason for compilation error. Commented Apr 8, 2015 at 8:44

5 Answers 5

2

You are telling the compiler that you want to call Object[].toString(). That's why the compiler generates a cast (checkcast):

 0: new           #2                  // class java/util/ArrayList
 3: dup
 4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
 7: astore_1
 8: aload_1
 9: new           #4                  // class java/lang/Object
12: dup
13: invokespecial #1                  // Method java/lang/Object."<init>":()V
16: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: astore_2
24: aload_2
25: iconst_0
26: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
31: checkcast     #7                  // class "[Ljava/lang/Object;"
34: invokevirtual #8                  // Method java/lang/Object.toString:()Ljava/lang/String;
37: pop
38: return

You can prevent the bytecode cast by adding a cast yourself in the Java code:

public static void main(String[] args) {
    List a = new ArrayList();
    a.add(new Object());
    List<Object[]> b = a;
    ((Object) b.get(0)).toString();
}

Now the compiler sees that a cast to Object[] is not needed since you only want an Object reference. The checkcast opcode is omitted:

 0: new           #2                  // class java/util/ArrayList
 3: dup
 4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
 7: astore_1
 8: aload_1
 9: new           #4                  // class java/lang/Object
12: dup
13: invokespecial #1                  // Method java/lang/Object."<init>":()V
16: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: astore_2
24: aload_2
25: iconst_0
26: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
31: invokevirtual #7                  // Method java/lang/Object.toString:()Ljava/lang/String;
34: pop
35: return
Sign up to request clarification or add additional context in comments.

Comments

2

Maybe it's clearer if you look at it like this:

import java.util.ArrayList;
import java.util.List;

class Dog { }
class Cat { }

public class CastTest {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(new Dog());
        List<Cat> b = a;
        Cat c = b.get(0);
    }
}

$ java CastTest
Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Cat
    at CastTest.main(CastTest.java:12)

If fact it's not true that generics don't affect the byte code. If you use javap to see the byte code for the above, you can see that a cast is generated to make sure the object is really a Cat before doing the assignment:

  ...
  26: invokeinterface #7,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
  31: checkcast     #8                  // class Cat
  34: astore_3      
  35: return   

If you really want it to be a List of Object arrays, you have to add an Object array:

import java.util.ArrayList;
import java.util.List;

public class CastTest {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(new Object[]{});
        List<Object[]> b = a;
        System.out.println(b.get(0).toString());
    }
}

$ java CastTest
[Ljava.lang.Object;@65685e30

2 Comments

it is solution, but not answer why JVM try cast item in list
Hi, @Akvel, see my latest edit to see the cast that gets generated in the byte code, as explained in the link you provided above (docs.oracle.com/javase/tutorial/java/generics/erasure.html) where it says that the compiler will Insert type casts if necessary to preserve type safety.
2

As others have said, because the Object is not Object[]

There is hint in the phrase java.lang.Object cannot be cast to [Ljava.lang.Object

According to JNI types and Data Structures, [Ljava.lang.Object means:

  • [ - an array
  • L - a Class

So java.lang.Object cannot be cast to [Ljava.lang.Object can be read as Object cannot be cast to Array of Object

Comments

0

Why are you casting it in list of Object array.

Try List of Object and it will work.

import java.util.ArrayList;
import java.util.List;

public class CastTest {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(new Object());
        List<Object> b = a;
        b.get(0).toString();
    }
}

1 Comment

Why - it another question, lets agree that this cast need for some reason :-) I understan that if I remove <Object[]> all will works fine.
0

This is because the compiler breaks

    b.get(0).toString();

down into

    Object[] temp = b.get(0);
    temp.toString();

which has a cast to Object[] when erased.

Now arguably, the compiler could also choose to break it down into

    Object temp = b.get(0);
    temp.toString();

since the reference is only used to call .toString() which is declared on Object. And if it did that it would avoid the cast. However, why would the compiler put in that extra analysis effort to do something in a different way, when it will only matter if you use the wrong types anyway?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.