Inconsistent ClassCastException for Raw Types

When executing the below code, the code is executed without error, but for a variable of type, the List<Integer>

return type of the method get()

must be integer, but when executing this code, when I call it x.get(0)

returns a string, 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 when executing the below code, simply adding the class lookup from the returned object to the previous code block throws a class exception. If the above code runs perfectly, the following should run without any exceptions:

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 do type conversion when retrieving the type of an object class?

+4


source to share


2 answers


The compiler has to insert typechecking instructions at the byte code level if necessary, so when assigning Object

, for example. Object o = x.get(0);

or System.out.println(x.get(0));

, it may not require it, calling a method in an expression x.get(0)

requires it.

The reason lies in the binary compatibility rules . In simple terms, it doesn't matter if the called method was inherited or explicitly declared as the type of the receiver, the formal type of the expression x.get(0)

is equal Integer

and you call the method on it getClass()

, hence the call will be encoded as a call to a method named getClass

with a signature () β†’ java.lang.Class

in the receiver class java.lang.Integer

. The fact that this method was inherited from java.lang.Object

and that it was declared final

at compile time is not reflected in the compiled class.

So, in theory, at runtime, a method could be removed from java.lang.Object

, and a new method java.lang.Class getClass()

added to java.lang.Integer

, without breaking compatibility with that particular code. While we know this will never happen, the compiler simply follows formal rules to avoid introducing inheritance assumptions into the code.

Since the call will be compiled as targeting calls java.lang.Integer

, there will be type casting before the call instruction, which will not be done in the Heap of Pollution script.



Please note that if you change the code to

System.out.println(((Object)x.get(0)).getClass());

      

you make the assumption explicit that the method was declared in java.lang.Object

. The extension to java.lang.Object

does not generate any additional bytecode command, the code does it all, changes the type of the method invocation receiver to java.lang.Object

, eliminating the need for a type.

There is an interesting deviation from the rule that the compiler encodes a call as a java.lang.Object

bytecode call if that method is one of the known methods final

declared in java.lang.Object

. This could be because this specific method is specified in the JLS and coding them in this form allows the JVM to identify these special methods quickly. But the combination of command checkcast

and command invokevirtual

still exhibits the same compatible behavior.

+5


source


This is due to PrintStream#println

:

public void println(Object x) {
    String s = String.valueOf(x);
    ...

      

See how it converts whatever you give to a string, but assign it first Object

(which works, because it Integer

is 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 get the same crash.

EDIT



Yes, Didier is right in his comment; So after thinking for a while about updating.

It could even be 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 there is no Integer

type, just Object

; which will constitute, 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;

      

Note checkcast

to check that the type we get from this List

is actually an integer Integer

. List::get

is a generic method, and this generic parameter will be Object

; to maintain correct List<Integer>

runtime is needed checkcast

.

+1


source







All Articles