Detailed explanation
Type erasure is the mechanism Java uses to implement generics while preserving compatibility with older, non-generic code and existing JVM class files. The compiler checks generic type usage at compile time, then translates it into code that uses ordinary classes and methods. For example, List<String> prevents you from adding an Integer through that reference, but Java does not create separate ArrayList classes for String and Integer. Consequently, l1.getClass() == l2.getClass() is true when both variables refer to ordinary ArrayList instances, regardless of their declared type arguments.
During erasure, an unbounded type parameter such as T becomes Object. A bounded parameter such as T extends Number becomes Number; with multiple bounds, the leftmost bound determines the erasure. The compiler also inserts casts where needed. For example, retrieving an element from a List<String> produces an erased method call returning Object, followed by a cast to String when a String result is required. To preserve polymorphism when erasure changes method signatures, the compiler can generate synthetic bridge methods that delegate to the appropriate overriding implementation.
It is important not to say that all generic information disappears. Class files can retain generic declarations in Signature metadata, and reflection can inspect them through APIs such as Field.getGenericType() or Class.getGenericSuperclass(). However, an ordinary ArrayList instance does not carry its actual element type argument as runtime class identity. Calling getClass() on a List<String> therefore cannot tell you that its elements were declared to be String. Reflection exposes retained declaration metadata, not a universally available runtime type argument for every object.
Erasure explains several interview favourites: you cannot write new T(), create new T[], or test instanceof List<String>; instanceof List<?> is allowed. Methods accepting List<String> and List<Integer> cannot be overloaded solely on those type arguments because their erased signatures clash. Raw types and unchecked casts can also cause heap pollution, allowing incompatible values into parameterised structures and causing ClassCastException later. Interviewers usually expect you to connect compile-time safety, erased runtime representation, and these practical restrictions rather than merely state that generics are removed.
Key points to remember
- Generics provide compile-time type checking without creating a separate runtime class for each type argument.
- Type parameters erase to Object or their leftmost bound.
- The compiler inserts casts and may generate bridge methods to preserve type behaviour and polymorphism.
- Reflection can inspect retained generic declarations, but getClass() does not reveal an ordinary object's type arguments.
- Erasure causes restrictions on parameterised instanceof checks, generic array creation, and overloads with identical erased signatures.
Common mistakes candidates make
- Claiming that reflection cannot access any generic information, ignoring retained declaration metadata.
- Saying every type parameter becomes Object, even when it has an explicit bound.
- Assuming List<String> and List<Integer> have different runtime classes or runtime-enforced element types.
Likely follow-up questions
What is a bridge method, and why does erasure require it?
Suppose Child implements Parent<String> and defines set(String), while Parent<T> declares set(T). The compiler generates a synthetic set(Object) bridge in Child that casts its argument and delegates to set(String), preserving overriding after erasure.
Why is instanceof List<?> allowed but instanceof List<String> rejected?
At runtime, Java can check whether an object implements List, but it cannot establish its String type argument. List<?> is reifiable because its type arguments are all unbounded wildcards, so the check requires no unavailable element-type information.
How can libraries obtain generic type information at runtime?
Libraries can inspect declared generic signatures, such as a field declared List<String> or an anonymous subclass extending a generic type with concrete arguments. They may also accept explicit Class<T> tokens, although a Class token alone cannot represent a nested parameterised type such as List<String>.
Interview tip: Define erasure, demonstrate the shared ArrayList runtime class, then mention bounds, inserted casts, and the reflection caveat.
