The information on generics is used by the compiler but is not available at runtime. This is called type erasure. Generics are implemented using an approach called type erasure: The compiler uses the generic type information to compile the code, but erases it afterward. Thus, the generic information is not available at runtime. This approach enables the generic code to be backward compatible with the legacy code that uses raw types.
The generics are present at compile time. Once the compiler confirms that a generic type is used safely, it converts the generic type to a raw type. For example, the compiler checks whether the following code in (a) uses generics correctly and then translates it into the equivalent code in (b) for runtime use. The code in (b) uses the raw type.
When generic classes, interfaces, and methods are compiled, the compiler replaces the generic type with the Object type. For example, the compiler would convert the following method in (a) into (b).
If a generic type is bounded, the compiler replaces it with the bounded type. For example, the compiler would convert the following method in (a) into (b).
It is important to note that a generic class is shared by all its instances regardless of its actual concrete type. Suppose list1 and list2 are created as follows:
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
Although ArrayList and ArrayList are two types at compile time, only one ArrayList class is loaded into the JVM at runtime. list1 and list2 are both instances of ArrayList, so the following statements display true:
System.out.println(list1 instanceof ArrayList);
System.out.println(list2 instanceof ArrayList);
However, the expression list1 instanceof ArrayList is wrong. Since ArrayList is not stored as a separate class in the JVM, using it at runtime makes no sense.
Because generic types are erased at runtime, there are certain restrictions on how generic types can be used. Here are some of the restrictions:
Restriction 1: Cannot Use new E()
You cannot create an instance using a generic type parameter. For example, the following statement is wrong:
E object = new E();
The reason is that new E() is executed at runtime, but the generic type E is not available at runtime.
Restriction 2: Cannot Use new E[]
You cannot create an array using a generic type parameter. For example, the following statement is wrong:
E[] elements = new E[capacity];
You can circumvent this limitation by creating an array of the Object type and then casting it to E[], as follows:
E[] elements = (E[])new Object[capacity];
However, casting to (E[]) causes an unchecked compile warning. The warning occurs because the compiler is not certain that casting will succeed at runtime. For example, if E is String and new Object[] is an array of Integer objects, (String[])(new Object[]) will cause a ClassCastException. This type of compile warning is a limitation of Java generics and is unavoidable.
Generic array creation using a generic class is not allowed, either. For example, the following code is wrong:
ArrayList<String>[] list = new ArrayList<String>[10];
You can use the following code to circumvent this restriction:
ArrayList<String>[] list = (ArrayList<String>[])new ArrayList[10];
However, you will still get a compile warning.
Restriction 3: A Generic Type Parameter of a Class Is Not Allowed in a Static Context
Since all instances of a generic class have the same runtime class, the static variables and methods of a generic class are shared by all its instances. Therefore, it is illegal to refer to a generic type parameter for a class in a static method, field, or initializer. For example, the
following code is illegal:
public class Test<E> {
public static void m(E o1) { // Illegal
}
public static E o1; // Illegal
static {
E o2; // Illegal
}
}
Restriction 4: Exception Classes Cannot Be Generic
A generic class may not extend java.lang.Throwable, so the following class declaration would be illegal:
public class MyException<T> extends Exception {
}
Why? If it were allowed, you would have a catch clause for MyException as follows:
try {
...
}
catch (MyException<T> ex) {
...
}
The JVM has to check the exception thrown from the try clause to see if it matches the type specified in a catch clause. This is impossible, because the type information is not present at runtime.
Top comments (0)