library
advancedType Erasure
Understand how generics are implemented via erasure and the limitations this creates.
The compiler checks your generics, then deletes them. The bytecode has no idea T ever existed.
Erasure = writing a letter with invisible ink (generics). The recipient (JVM) can't see the invisible parts — they only see the regular ink (Object, casts).
Key Concepts
1
// what you write
class Box<T> { T value; T get() { return value; } }
List<String> names = new ArrayList<>();
String s = names.get(0);2
// what the bytecode holds
class Box { Object value; Object get() { return value; } }
List names = new ArrayList();
String s = (String) names.get(0); // cast inserted for you3
An unbounded T becomes Object. A bounded one becomes its bound:
TObject
4
<T> -> Object
<T extends Number> -> Number
<T extends Comparable> -> Comparable5
Why it was done this way: backward compatibility. Java 5 generics had to run on bytecode compiled before
generics existed, and old code passing a plain List had to keep working. Erasure made List<String> and
List the same class at runtime, so both directions compile.
ListList<String>
6
What it costs you.
new T() the type is unknown at runtime
new T[10]
obj instanceof T
T.class
static T field statics are shared across all T
f(List<String>) and f(List<Integer>) same erasure, cannot overload7
And this is the surprising consequence:
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass(); // true -- both are ArrayList8
So nothing at runtime can tell you what a collection was declared to hold.
9
The workaround is a type token — pass the class explicitly:
10
<T> T parse(String json, Class<T> type) { ... }
parse(body, Order.class);11
Or capture it from a subclass. Generic information on a class declaration is kept in the class file,
which is why new TypeReference<List<Order>>() {} works in Jackson: the anonymous subclass records the type
even though a variable could not.
new TypeReference<List<Order>>() {}
12
What the interviewer is probing.1. "What is type erasure and why did Java choose it?" Probing: whether you know the trade-off,
not just the term. Stalls: "Generics are removed at runtime." Moves up: it was for backward
compatibility: generic code had to interoperate with pre-generic bytecode, so List<String> and List
are the same class at runtime.
13
2. "List<String> and List<Integer> — same class at runtime?" Probing: the consequence of
erasure. Stalls: "No, they differ." Moves up: yes, getClass() returns the same ArrayList, which
is why you cannot overload on them or test them with instanceof.
14
3. "How does Jackson read the type for new TypeReference<List<Order>>() {}?" Probing: the
exception to erasure. Stalls: "It guesses." Moves up: generic information on a class declaration
is kept in the class file, so the anonymous subclass records the type a variable could not.
15
4. "How do you work around not being able to write T.class?" Probing: the type token idiom.
Stalls: "You cannot." Moves up: pass Class<T> as a parameter and use it for the cast or
instantiation, which is what parse(json, Order.class) does.