泛型的类型擦除(Type Erasure) 是 Java 泛型八股里非常高频的知识点。

先记一句话:

Java 的泛型主要存在于编译阶段,编译成字节码后,大部分泛型类型信息会被擦除。

1. 举个最直观的例子

你写:

List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();

源码层面明显不同:

List<String>
List<Integer>

但是运行时:

System.out.println(list1.getClass() == list2.getClass());

结果:

true

因为运行时它们本质上都是:

ArrayList

可以粗略理解成编译过程中:

List<String>  ─┐
               ├── 类型擦除 ──→ List
List<Integer> ─┘

所以 JVM 并不会因为一个是 String,一个是 Integer,就生成两个不同的 ArrayList 类。

2. 那泛型还有什么意义?

你可能会问:

既然最后都擦除了,那 List<String> 有什么用?

因为泛型最主要的作用发生在编译阶段。

例如:

List<String> list = new ArrayList<>();
 
list.add("hello"); // OK
list.add(123);     // 编译报错

编译器知道:

这是 List<String>
      ↓
只能放 String
      ↓
Integer 不允许

因此:

泛型负责在编译阶段提供类型安全,类型检查完成后,再进行类型擦除。


**3. T

被擦除之后变成什么?

例如:

class Box<T> {
 
    private T data;
 
    public T getData() {
        return data;
    }
}

类型擦除后,可以粗略理解成:

class Box {
 
    private Object data;
 
    public Object getData() {
        return data;
    }
}

因为 T 没有限定,所以通常擦除成 Object。

但我们使用的时候:

Box<String> box = new Box<>();
String s = box.getData();

为什么不用自己写:

String s = (String) box.getData();

因为编译器会帮你处理必要的类型转换。

所以可以理解成:

程序员写:
 
String s = box.getData();
 
        ↓ 编译器处理
 
类似:
 
String s = (String) box.getData();

4. 如果泛型有限制呢?

例如:

class Box<T extends Number> {
 
    private T data;
}

这里:

T extends Number

那么擦除后,T 不再简单地变成 Object,而是以它的上界为擦除结果,可以粗略理解:

class Box {
 
    private Number data;
}

所以记:

<T>                  → Object
 
<T extends Number>   → Number

更准确地说,存在多个上界时,擦除使用最左边的上界。


5. 类型擦除带来的经典限制

因为:

List<String>
List<Integer>

运行时泛型参数大部分已经被擦除了,所以不能这样判断:

if (obj instanceof List<String>) { // ❌
}

通常只能:

if (obj instanceof List<?>) { // ✅
}

同样,你也不能直接:

new T(); // ❌

因为运行时并不知道这里的 T 具体应该创建什么类型。


面试怎么回答?

面试官问:

什么是 Java 泛型的类型擦除?

你可以回答:

Java 的泛型主要是在编译阶段实现的。编译器会利用泛型信息进行类型检查,保证类型安全;编译之后,大部分泛型类型信息会被擦除。

比如 List<String> 和 List<Integer> 在运行时实际上都是同一个 List 类型。如果泛型参数没有上界,通常会擦除为 Object;如果存在上界,比如 <T extends Number>,则会按照它的上界进行擦除。

编译器还会在需要的位置自动插入类型转换。因此 Java 泛型可以保证编译期类型安全,同时又能兼容泛型出现之前的代码。

最后压缩成一句口诀:

泛型:编译时检查,运行时擦除;无上界擦成 Object,有上界擦成对应上界。

不过要注意,“运行时完全没有任何泛型信息”这种说法也不严谨——类、字段、方法签名中有些泛型元数据仍可以通过反射获取。八股里说大部分泛型类型信息被擦除更准确。