Generics
Type Erasure
Generics are a compile-time construct: after compilation,
Pair<String> and Pair<Integer> are the same class Pair, with type variables replaced by their bounds and casts inserted at use sites. Every generics limitation traces back to this.- One class file per generic type — type arguments vanish at runtime
- T erases to its first bound (
Objectif unbounded) - The compiler inserts synthetic casts at reads and bridge methods for inheritance
- No runtime type argument info:
instanceof List<String>is illegal - Erasure bought seamless migration compatibility with pre-generics code
// You write:
Pair<Employee> buddies = new Pair<>(e1, e2);
Employee first = buddies.getFirst();
// After erasure the JVM sees:
Pair buddies = new Pair(e1, e2); // raw Pair — T became Object
Employee first = (Employee) buddies.getFirst(); // compiler-inserted castJava chose erasure so generic code could interoperate with a decade of existing libraries without recompilation — unlike C++ templates (a class per instantiation) or C# reified generics (runtime type info). The price: the runtime cannot distinguish List<String> from List<Integer>; both are just List.
Bridge methods
class DateInterval extends Pair<LocalDate> {
@Override
public LocalDate getSecond() { ... } // your override
}
// Erased Pair has: Object getSecond()
// The compiler synthesizes a bridge in DateInterval:
// Object getSecond() { return getSecond(); } // calls YOUR LocalDate version
// — preserving polymorphism across the erased signature.Bridges surface in stack traces and reflection (Method.isBridge()). They also implement covariant return types for non-generic code — the same mechanism.