Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do Java generics use casting at runtime?

Tags:

java

generics

I was reading a discussion about C++ templates and C# generics, and how they are different from Java's type-erased generics. I read a statement that said that Java still uses casting at runtime, for instance when dealing with collections. If this is true, I wasn't aware of it!

Let's say that I have code such as:

ArrayList<SomeClass> list = new ArrayList<SomeClass>();
...
SomeClass object = list.get(0);

My question is. Is this effectively compiled to

ArrayList list = new ArrayList();
...
SomeClass object = (SomeClass) list.get(0);

If so, why? I thought that the fact that list is of type ArrayList<SomeClass> guarantees, at compile time and run time, that only SomeClass will be stored inside the ArrayList? Or can you ever do unsafe type-casting to transform an ArrayList<OtherClass> into an ArrayList<SomeClass>?

Are there other occasions where at-runtime type casting is done in Java generics?

Finally, if casting at run time is indeed used, are there occasions when the JIT can elide the run time cast check?

(Please refrain from answering/commenting that micro-optimisations are not worth it, preemptive optimisation is root of all evil, etc. I see these on other similar questions. These points are well understood, but they do not take away the point of trying to understand how type-erased generics are implemented under the hood.)

like image 688
Max Avatar asked Apr 12 '12 14:04

Max


2 Answers

Here is a short program I wrote:

public class Test<T> {

  public T contents;

  public Test(T item) {
    contents = item;
  }

  public static void main(String[] args) {

    Test<String> t = new Test<String>("hello");
    System.out.println(t.contents);

  }

}

Try compiling it with javac, and then look at the bytecode with javap -verbose. I've selected a few interesting lines:

public java.lang.Object contents;

This should turn up just before the definition of the Test constructor. In the example code it was of type T, now it is an Object. That's erasure.

Now, looking at the main method:

public static void main(java.lang.String[]);
Code:
   Stack=3, Locals=2, Args_size=1
   0:   new #3; //class Test
   3:   dup
   4:   ldc #4; //String hello
   6:   invokespecial   #5; //Method "<init>":(Ljava/lang/Object;)V
   9:   astore_1
   10:  getstatic   #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   13:  aload_1
   14:  getfield    #2; //Field contents:Ljava/lang/Object;
   17:  checkcast   #7; //class java/lang/String
   20:  invokevirtual   #8; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   23:  return

Then we can see the checkcast command at line 17, just before the println - this is where Java casts from Object to the erased generic type - String

like image 155
Richante Avatar answered Oct 25 '22 15:10

Richante


Your assumption is correct.

The type check is always necessary because you can write the following legal code:

ArrayList<X> good = new ArrayList<X>();
ArrayList q = x;
ArrayList<Y> evil = (ArrayList<Y>)q;  //Doesn't throw due to type erasure
evil.add(new Y()); //this will actually succeed
X boom = good.get(0);

The cast from ArrayList to ArrayList<Y> will (always) give an unchecked cast warning, but will (also always) succeed at runtime.

like image 27
SLaks Avatar answered Oct 25 '22 13:10

SLaks