Type erasure: generics at runtime
What the JVM actually sees — and the four concrete consequences that appear on the OCP.
Generics exist only at compile time
The compiler checks generic constraints and then erases all type arguments from the bytecode. At runtime List<String> and List<Integer> are the same class:
List<String> ss = new ArrayList<>();
List<Integer> ii = new ArrayList<>();
System.out.println(ss.getClass() == ii.getClass()); // true
Erasure replaces each type parameter with its erasure — the first bound if bounded, Object if unbounded. So T extends Number erases to Number; an unbounded T erases to Object.
### Four concrete consequences
1. No new T() or new T[]
The JVM has no idea what T is at runtime, so it cannot allocate one:
// Both are compile errors:
// T obj = new T();
// T[] arr = new T[10];
2. No instanceof with a parameterised type
// if (obj instanceof List<String>) { } // compile error
if (obj instanceof List<?>) { } // OK — unbounded wildcard
3. Cannot overload on erased types
// Compile error — both erase to process(List)
void process(List<String> xs) { }
void process(List<Integer> xs) { }
After erasure both signatures are identical: void process(List). The compiler rejects the duplicate.
4. Unchecked-cast warnings
Casting to a parameterised type cannot be checked at runtime:
@SuppressWarnings("unchecked")
List<String> list = (List<String>) someRawList; // warning, not error
The cast succeeds at runtime (it only checks List), but elements retrieved later may throw ClassCastException.
### Why erasure exists
Backward compatibility: pre-Java-5 code that uses raw List must still run on modern JVMs. Erasure means the same bytecode works for both old and new code.