Why Does Java’s Generic Type Erasure Make List<String> Equal List<Integer>?
This article explains Java's generic type erasure, showing why different generic collections share the same runtime class, detailing raw types, the impact on instanceof and new operations, array creation limits, overload conflicts, and how to work around these constraints.
Java Generic Type Erasure
In Java, generics are often called “pseudo‑generics” because the actual type arguments are removed at runtime through a process called type erasure.
Generics exist only at compile time; at runtime the generic type information disappears, so List and List are both just List.
Principle of Type Erasure
Example code demonstrates that two ArrayList instances with different type parameters have the same runtime class.
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());
}
}The statement prints true because the generic types String and Integer are erased, leaving only the raw type.
Raw Types Explained
A raw type is the original type without generic information. When a type variable has no bounds, its raw type is Object.
class College<T> {
private T student;
public College(T student) { this.student = student; }
public T getStudent() { return student; }
public void setStudent(T student) { this.student = student; }
}After compilation, College becomes a raw type because T is unbounded, so its raw type is Object.
If the type variable has bounds, the raw type is the first bound. For example:
class College<T extends Comparable & Serializable> { }Here the raw type is Comparable.
Problems Caused by Erasure and Solutions
1. Cannot use generic type in instanceof or new expressions
class Erased {
public void f(T t, String a) {
// T t = new T(); // error
// T[] ts = new T[100]; // error
// boolean k = a instanceof T; // error
}
}A common workaround is to pass a Class object representing the concrete type.
class Erased {
Class kind;
public Erased(Class kind) { this.kind = kind; }
public void f(String a) throws Exception {
T t = (T) kind.newInstance(); // requires default constructor
T[] ts = (T[]) Array.newInstance(kind, 10);
boolean k = kind.isInstance(a);
}
}2. Primitive types cannot be used as type arguments
Because erasure replaces the type variable with its upper bound, which is a class, primitive types are incompatible. Autoboxing can be used, but note the difference between int[] and Integer[].
3. Generic arrays
You may declare an array of a generic type, but you cannot create one directly.
List[] stringLists; // declaration is allowed
List[] stringLists2 = new ArrayList[1]; // creation is illegalUse Array.newInstance(Class, int) to create a generic array.
4. Overloading conflicts
class Holder<t, e> {
void f(List list) {}
void f(List list) {} // compile error: same erasured signature
}5. Base‑class hijacking of generic interfaces
interface Run { void with(T t); }
class Animal implements Run {
@Override public void with(Integer integer) {}
}
class Dog extends Animal implements Run {} // compile errorThe subclass inherits the generic interface from the superclass, causing a conflict when the type arguments differ.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Mike Chen's Internet Architecture
Over ten years of BAT architecture experience, shared generously!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
