Memra

Type erasure & its consequences

Generics 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)
NORMAL ~/memra/learn/comp-308/type-erasure-and-consequences utf-8 LF