Does type erasure of Java Generics cause full type casting?
The short story: Yes, there is a type check. Here's the proof -
Given the following classes:
// Let's define a generic class.
public class Cell<T> {
public void set(T t) { this.t = t; }
public T get() { return t; }
private T t;
}
public class A {
static Cell<String> cell = new Cell<String>(); // Instantiate it.
public static void main(String[] args) {
// Now, let's use it.
cell.set("a");
String s = cell.get();
System.out.println(s);
}
}
The bytecode that A.main()
is compiled into (decompiled via javap -c A.class
) is as follows:
public static void main(java.lang.String[]);
Code:
0: getstatic #20 // Field cell:Lp2/Cell;
3: ldc #22 // String a
5: invokevirtual #24 // Method p2/Cell.set:(Ljava/lang/Object;)V
8: getstatic #20 // Field cell:Lp2/Cell;
11: invokevirtual #30 // Method p2/Cell.get:()Ljava/lang/Object;
14: checkcast #34 // class java/lang/String
17: astore_1
18: getstatic #36 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_1
22: invokevirtual #42 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: return
}
As you can in offset 14
, the result of cell.get()
is type checked to verify that is indeed a string:
14: checkcast #34 // class java/lang/String
This does incur some runtime penalty. However, as the JVM is pretty well optimized, the effect of this is likely to be minimal.
The longer story:
How would you go about implementing such a CheesecakeList
class? Wouldn't this class define an array to hold the elements? Be advised that every assignment to an array incurs a hidden type check. So you won't gain as much as you think (although it is likely that your program will perform more read operations than write operations so a Cheesecake[]
array will give you something).
Bottom line: don't optimize prematurely.
A final comment. People often think that type erasure means that Cell<String>
is compiled into Cell<Object>
. That's not true. The erasure is applied only to the definition of the generic class/method. It is not applied to the usage sites of these classes/methods.
In other words, the class Cell<T>
is compiled as if it were written as Cell<Object>
. If T
has an upper bound (say Number
) then it is compiled into Cell<Number>
. In other places in the code, typically variables/parameters whose type is an instantiation of the Cell
class (such as Cell<String> myCell
), the erasure is not applied. The fact that myCell
is of type Cell<String>
is kept in the classfile. This allows the compiler to type check the program correctly.
The type-checking is done at compile-time. If you do this:
List<Cheesecake> list = new ArrayList<Cheesecake>();
then the generic types can be checked at compile-time. This erases to:
List list = new ArrayList();
which is no different to any other up-cast (e.g. Object o = new Integer(5);
).