Type erasure & its consequences
◈ 5 cardsGenerics are a compile-time fiction — erasure removes them at runtime, which forbids new T(), generic arrays, and runtime type tests.
What erasure is
Java generics are implemented by type erasure: the type parameters exist only at compile time. After the compiler verifies your types and inserts casts, it erases the parameters from the bytecode. List<String> and List<Integer> become the same class — plain List — at runtime:
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass()); // true — both are ArrayList
An unbounded T erases to Object; a bounded <T extends Number> erases to its leftmost bound, Number. This design kept generics backward-compatible with pre-1.5 bytecode, but it means the JVM has no idea about your type arguments. Several things follow directly.
What you cannot do, and why
1. new T() is illegal. The runtime does not know what T is, so it cannot allocate one:
<T> T make() { return new T(); } // COMPILE ERROR
2. instanceof T and instanceof List<String> are illegal. The runtime type carries no parameter, so the test is meaningless. Only the unbounded form is allowed:
if (x instanceof List<String>) {} // COMPILE ERROR
if (x instanceof List<?>) {} // OK — no parameter to check
3. You cannot create a generic array. new T[10] and new List<String>[10] are forbidden, because arrays know and check their element type at runtime but generics do not — the two models clash. Use a collection (List<T>) or an Object[] with casts instead.
The Class<T> token workaround
Because new T() is impossible, the idiom is to pass the class object as a runtime token. The Greenhouse project uses exactly this to build an Event from a class — and a generic factory looks like:
static <T> T create(Class<T> type) throws Exception {
return type.getDeclaredConstructor().newInstance();
}
String s = create(String.class); // type token supplies the runtime type
Class<T> survives erasure (it is an ordinary object you pass in), so newInstance() has the runtime type information that new T() lacks.
One more consequence: no overloading by parameter
Because both erase to the same signature, you cannot overload a method on the type argument:
void f(List<String> s) {}
void f(List<Integer> i) {} // COMPILE ERROR — same erasure: f(List)