简体   繁体   中英

When do Java generics use casting at runtime?

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.)

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.

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

Remember that at runtime, an ArrayList<String> is the same as an ArrayList and is implemented with an underlying Object[] . The JIT is often smart enough to elide the casts, yes, but since generics are erased at runtime, the casts are necessary.

Java is doing great job to maintain compatibility between its versions which means that Java5+ syntax can be compiled into 1.4 compatible classes. I guess that is part of the reason why ArrayList does not guarantee at run time that only SomeClass will be stored inside the ArrayList.

And to see what your generics code really compiles into please use the excellent JAD tool.

Your code indeed compiles into SomeClass object = (SomeClass) list.get(0);

I thought that the fact that list is of type ArrayList 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 into an ArrayList?

Yes, type information is discarded at run-time.

It means that you can't understand if you have an object of type ArrayList<String> or an an object of type ArrayList<Long> . JVM doesn't know the actual type arguments for instances of generic classes so you do not have that run-time checks (and you'll see errors only when you'll consume that data).

This code is valid:

public void enqueueItem(ArrayList<Integer> list, Integer item) {
    List listAlias = list;
    listAlias.add(x.toString());
}

It's not a problem at run-time, the type checking is still there to save you from any issue and you can interop old code with new code. Even in the .NET environment you have the same behavior ( IList<T> derives from IList ).

What you pay is only the performance impact of a cast (and this is the big difference with the Java's generic implementation). More or less generics are a compile-time helper to detect errors and to make your code more readable . For example this code is pretty valid for the compiler:

public ArrayList getCollection() {
   return new ArrayList<Integer>();
}

public void doStuff() {
   ArrayList<String> list = getCollection();
   list.add("text");
}

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

Yes, everytime you consume them (that's the point where you'll get an error if you do something bad with generics).

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

I think no, it always does a cast but this is a very implementation detail anyway it tracks the type arguments for subclasses so if you derive your own class from a generic class then you'll be saved from this issue .

If so, why?

All the existing code will work with new generics, even if you change some interface. It means you can update to generics step by step. I do not worry about the performance impact (we survived with it for a long time, is it a problem now?) but I do not like all this tricks. Maybe, after all, we can't have the oak full and the wife drank ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM