I do not understand how the compiler handle's the following code as it outputs Test while I was expecting an error.
List<Integer> b = new ArrayList<Integer>();
List a = b;
a.add("test");
System.out.println(b.get(0));
I was hoping someone could tell me the exact steps the compiler goes through when executing the code so I can understand the output. My current understanding is that:
If there is no add(Object e) method in in the actual object List<Integer> how does it still somehow add a String to the List of Integers?
You are quite close. The compile time checks all pan out:
a
is of type List
so the call
a.add("test");
pans out. b
is of (compile-time) type ArrayList<Integer>
so
b.get(0)
checks out as well. Note that the checks are made only against the compile-time types of the variables. When the compiler sees a.add("test")
it does not know the run time value of the object referenced by variable a
. In general, it really can't (there's a result in theoretical computer science about this), though control-flow type analysis can catch many such things. Languages like TypeScript can do amazing things at compile time.
Now you might assume that at run-time such things could be checked. Alas, in Java they cannot. Java erases generic types. Find an article on Java type erasure for the gory details. The TL;DR is that a List<Integer>
at compile time becomes a raw List
at run time. The JVM did not have a way to "reify" generics (though other languages do!) so when generics were introduced, the decision was made that Java would just erase the generic types. So at run time, there is no type problem in your code.
Let's take a look at the compiled code:
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: astore_2
10: aload_2
11: ldc #4 // String test
13: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
18: pop
19: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
22: aload_1
23: iconst_0
24: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
29: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
32: return
Here you can see directly that there are no run-time type checks. So, the complete (but seemingly flippant) answer to your question is that Java only checks types at compile time based on the types of the variables (known at compile time), but generic type parameters are erased and the code is run without them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With