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