13

I have following code:

public static void main(String[] args) {
    List<String> s = new ArrayList<String>();
    s.add("kshitiz");

    //This is not typesafe. It should blow up at runtime
    List<Integer> i = new ArrayList(s);
    System.out.println(i.get(0));
}

This program runs fine and it prints kshitiz. It only fails if I replace the last line with:

System.out.println(i.get(0).getClass());

Exception:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

What is happening here?

DavidS
  • 5,022
  • 2
  • 28
  • 55
Kshitiz Sharma
  • 17,947
  • 26
  • 98
  • 169

2 Answers2

13

I guess you are aware, that generic types are gone at run time. Now, to see what's happening behind the scenes, let's look at this piece of code

public static Class<?> getClass(final Object object) {
    return object.getClass();
}

public static void main(final String[] args) {
    final List<String> s = new ArrayList<String>();
    s.add("kshitiz");

    // This is not typesafe. It should blow up at runtime
    final List<Integer> i = new ArrayList(s);
    System.out.println(getClass(i.get(0)));
    System.out.println(i.get(0).getClass());
}

and the output of javap -c

  [...]
  26: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
  29: aload_2
  30: iconst_0
  31: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
  36: invokestatic  #10                 // Method getClass:(Ljava/lang/Object;)Ljava/lang/Class;
  39: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  42: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
  45: aload_2
  46: iconst_0
  47: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
  52: checkcast     #12                 // class java/lang/Integer
  55: invokevirtual #2                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
  58: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  61: return

So you see that in the second call, the String is cast to an Integer, while in the first case, it is regarded as an object. Thus as long as the String is handled just like any other object, everything is fine, but as soon as you call a method of the element type of the list, the object is cast to that type.

muued
  • 1,666
  • 13
  • 25
0

Generics in java are broken by design.

In your List<Integer> i = new ArrayList(s); you have a raw ArrayList. It means that you ignore type parameter and it works like if it was an Object. Then you (implicitly) cast an ArrayList to List<Integer>, but generics are not checked at runtime, so the JVM doesn't care about mismatch.

When you do System.out.println(i.get(0)) you don't convert String to Integer because 'println(Object)` method is invoked.

In System.out.println(i.get(0).getClass()) this magic brakes because for some reason (you probably can find this reason in specs, but I suggest you not to) your String gets casted to Integer.

Droidman
  • 11,485
  • 17
  • 93
  • 141
talex
  • 17,973
  • 3
  • 29
  • 66
  • 3
    You may discuss whether it’s better if the code fails at the assignment of `ArrayList` to `ArrayList` or, as it did, when calling `get`, however, I do not see enough difference to justify calling the current behavior “broken by design”. After all, the result is the same, ignoring a compile-time warning leads to a runtime exception… – Holger Apr 23 '15 at 16:33