面试知识库
进阶

Java泛型与类型擦除#

一句话答案#

Java 泛型在编译期检查类型安全,编译后擦除为 Object(或上界),运行时无法获取泛型实际类型。

核心要点

泛型的本质:编译期类型检查,运行期擦除

// 编译前(有泛型信息)
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);   // 编译器自动插入 (String) 强转

// 编译后(类型擦除,字节码中没有泛型信息)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);   // 编译器插入的强制类型转换
java

类型擦除规则:

① 无界泛型 <T>        → 擦除为 Object
② 有界泛型 <T extends Number> → 擦除为 Number(上界)
③ 所有泛型类型参数在运行时消失,JVM 只看到原始类型
plaintext

类型擦除带来的限制:

// 1. 不能 new 泛型数组
T[] arr = new T[10];              // 编译错误

// 2. 不能 instanceof 泛型
if (obj instanceof List<String>)  // 编译错误,运行时无法区分 List<String> 和 List<Integer>

// 3. 不能 new 泛型对象
T t = new T();                    // 编译错误

// 4. 静态方法/字段不能使用类的泛型参数
class Foo<T> {
    static T value;               // 编译错误
}
java

PECS 原则(Producer Extends, Consumer Super):

PECS 经典应用(Collections.copy 签名):

public static <T> void copy(List<? super T> dest,    // 消费者:往里写
                             List<? extends T> src) { // 生产者:从中读
    for (int i = 0; i < src.size(); i++)
        dest.set(i, src.get(i));
}
java
面试回答(2分钟版)

Java泛型的本质是编译期类型检查,编译后类型信息会被擦除。具体来说,编译器在编译阶段利用泛型信息做类型安全检查,自动插入强制类型转换代码,编译完成后泛型参数被擦除为Object(有上界的擦除为上界类型),运行时JVM完全看不到泛型信息。这就是为什么不能new T()、不能new T[]、不能instanceof List——这些操作需要运行时类型信息而泛型已经被擦除了。通配符是泛型使用中的重点,遵循PECS原则:Producer Extends Consumer Super。读数据的场景用? extends,比如List<? extends Number>可以安全读出Number但不能写入;写数据的场景用? super,比如List<? super Integer>可以安全写入Integer但读出只能是Object。Collections.copy的签名就是PECS的经典应用:dest用super消费数据,src用extends生产数据。虽然运行时泛型被擦除,但通过反射获取泛型父类的ParameterizedType仍可以拿到泛型实际类型,这也是很多框架如Jackson做类型推断的原理。

追问与易错

追问方向:

  • “类型擦除后怎么保证类型安全?”→ 编译期做完整的泛型类型检查,编译器自动插入强制类型转换代码(checkcast 字节码),运行时靠这些转换保证类型正确
  • “运行时能获取泛型类型吗?”→ 可以通过反射获取泛型父类/泛型字段的 ParameterizedType(如 getGenericSuperclass()),这也是 Jackson、MyBatis 等框架做类型推断的原理
  • “泛型数组为什么不能直接创建?”→ 类型擦除后 new T[] 变成 new Object[],无法在运行时进行数组的类型检查(ArrayStoreException),会破坏类型安全

易错点:

  • ❌ “Java 泛型和 C++ 模板一样”——Java 是擦除式泛型,运行时无类型信息
  • ❌ 混淆 <? extends T><? super T>——PECS 原则:生产者 extends 消费者 super