11.11 Type Erasure
Understanding translation by type erasure aids in understanding the restrictions and limitations that arise when using generics in Java. Although the compiler generates generic-free bytecode, we can view the process as a source-to-source translation that generates non-generic code from generic code.
The translated code has no information about type parameter. That is, the type parameters have been erased—hence the term type erasure. This involves replacing the usage of the type parameters with concrete types, and inserting suitable type conversions to ensure type correctness. In certain situations, bridge methods are also inserted for backward compatibility.
Translation by Type Erasure
The process of determining the erasure of a type—that is, what a type in the source code should be replaced with—uses the following rules:
Drop all type parameter specifications from parameterized types.
Replace any type parameter as follows:
- Replace it with the erasure of its bound, if it has one.
- Replace it with Object, if it has none.
- Replace it with the erasure of the first bound, if it has multiple bounds.
Table 11.4 shows examples of translation by erasure for some representative types, and the rules that are applied.
Table 11.4 Examples of Type Erasure
Type | Erasure | Rule no. |
List<E> List<Integer> List<String> List<List<String>> List<? super Integer> List<? extends Number> | List | 1 |
List<Integer>[] | List[] | 1 |
List | List | 1 |
int | int | For any primitive type |
Integer | Integer | For any non-generic type |
class Subclass extends Superclass implements Comparable<Subclass> {…} | class Subclass extends Superclass implements Comparable {…} | 1 |
public static <T extends Comparable<? super T>> T max(T obj1, T obj2) { … } | public static Comparable max(Comparable obj1, Comparable obj2) { … } | 2a. The first bound is Comparable. |
public static <T> T doIt(T t) { T lv = t; } | public static Object doIt(Object t) { Object lv = t; } | 2b |
T extends MyClass & Comparable<T> & Serializable | MyClass | 2c. The first bound is MyClass. |
The following code mixes legacy and generic code. Note that a ClassCastException is expected at (5) because the type-safety of the stack of String has been compromised.
// Pre-erasure code
List<String> strList = new ArrayList<>(); // (0)
List list = strList; // (1) Assignment to non-generic reference is ok.
strList = list; // (2) warning: unchecked conversion
strList.add(“aha”); // (3) Method call type-safe.
list.add(23); // (4) warning: [unchecked] unchecked call to add(E)
// as a member of the raw type java.util.List
System.out.println(strList.get(1).length()); // (5) ClassCastException
It is instructive to compare the corresponding lines of code in the pre-erasure code above and the post-erasure results shown below. A cast is inserted to convert from Object type to String type at (5′). This is necessary because post-erasure code can only get an Object from the list, and in order to call the length() method, the reference value of this object must be converted to String. It is this cast that is the cause of the exception at runtime.
// Post-erasure code
List strList = new ArrayList(); // (0′)
List list = strList; // (1′)
strList = list; // (2′)
strList.add(“aha”); // (3′)
list.add(Integer.valueOf(23)); // (4′)
System.out.println(((String)strList.get(1)).length()); // (5′) Cast inserted.
Leave a Reply