When executing the below code, the code is executed perfectly without any errors, but for a variable of type List<Integer>
, the return type of get()
method should be Integer, but while executing this code, when I call x.get(0)
a string is returned, whereas this should throw an exception.
public static void main(String[] args)
{
ArrayList xa = new ArrayList();
xa.addAll(Arrays.asList("ASDASD", "B"));
List<Integer> x = xa;
System.out.println(x.get(0));
}
But while executing the below code, just adding the retrieval of class from the returned object to the previous code block throws a class cast exception. If the above code executes perfectly the following should also execute without any exception:
public static void main(String[] args)
{
ArrayList xa = new ArrayList();
xa.addAll(Arrays.asList("ASDASD", "B"));
List<Integer> x = xa;
System.out.println(x.get(0).getClass());
}
Why does java execute a type conversion while fetching the class type of the object?
The compiler has to insert type checking instructions at the byte code level where necessary, so while an assignment to Object
, e.g. Object o = x.get(0);
or System.out.println(x.get(0));
, may not require it, invoking a method on the expression x.get(0)
does require it.
The reason lies in the binary compatibility rules. Simply said, it is irrelevant whether the invoked method has been inherited or explicitly declared by the receiver type, the formal type of the expression x.get(0)
is Integer
and you are invoking the method getClass()
on it, hence, the invocation will be encoded as an invocation of a method named getClass
with the signature () → java.lang.Class
on the receiver class java.lang.Integer
. The facts that this method has been inherited from java.lang.Object
and that it was declared final
at compile time, are not reflected by the compiled class.
So in theory, at runtime, the method could have been removed from java.lang.Object
and a new method java.lang.Class getClass()
added to java.lang.Integer
without breaking the compatibility to that specific code. While we know that this will never happen, the compiler is just following the formal rules not to inject assumptions about the inheritance into the code.
Since the invocation will be compiled as an invocation targeting java.lang.Integer
, a type cast is necessary before the invocation instruction, which will fail in the Heap Pollution scenario.
Note that if you change the code to
System.out.println(((Object)x.get(0)).getClass());
you will make the assumption explicit that the method has been declared in java.lang.Object
. The widening to java.lang.Object
will not generate any additional byte code instruction, all this code does, is changing method invocation’s receiver type to java.lang.Object
, eliminating the need for a type cast.
There is an interesting deviation from the rules here, that the compiler does encode the invocation as an invocation on java.lang.Object
on the bytecode level, if the method is one of the known final
methods declared in java.lang.Object
. This might be due to the fact that these specific method are specified in the JLS and encoding them in this form allows the JVM to identify these special methods quickly. But the combination of the checkcast
instruction and the invokevirtual
instruction still exhibits the same, compatible behavior.
It's because of the PrintStream#println
:
public void println(Object x) {
String s = String.valueOf(x);
...
See how it converts anything you give it to a String, but first assigning it to an Object
(which works because Integer
is an Object
). Change your first code to:
ArrayList xa = new ArrayList();
xa.addAll(Arrays.asList("ASDASD", "B"));
List<Integer> x = xa;
Integer i = x.get(0);
System.out.println(i);
and you will get the same failure.
EDIT
Yes, Didier is right in his comment; thus after thinking for a while the update.
This can the even simplified like this to understand why the compiler is inserting the extra checkcast #5 // class java/lang/Integer
:
ArrayList<Integer> l = new ArrayList<>();
l.get(0).getClass();
At runtime the there's no Integer
type, just plain Object
; which would compile among other things to :
10: invokevirtual #4 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
13: checkcast #5 // class java/lang/Integer
16: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class;
Notice the checkcast
to check that the type that we get from that List
is actually an Integer
. List::get
is a generic method, and that generic parameter at runtime would be an Object
; to maintain the correct List<Integer>
at runtime the checkcast
is needed.
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