Java 泛型为什么要类型擦除?List<String> 和 List<Integer> 在运行时是不同类型吗?
下面是一段教学用的模拟面试。
🧑💻 面试官: Java 泛型的类型擦除是什么意思?
🙋♂️ 我: 编译后泛型类型就没有了,都变成 Object。
🧑💻 面试官: 有上界的类型参数也一定变成 Object 吗?反射为什么有时还能读到泛型信息?
🙋♂️ 我: 可能还有部分声明信息。
🧑💻 面试官: 那把一个 Integer 混进 List
泛型没有“全部消失”,也不会给每个集合对象都装上元素类型检查。要分清编译约束、运行时表示和声明元数据。
面试速答(60 秒版)
Java 泛型主要在编译期检查类型,并通过类型擦除保持与既有运行时和代码的兼容。
擦除时,无界类型参数通常变成 Object;有界类型参数会按相应规则擦除为上界,不是全部都变成 Object。编译器还可能插入类型转换,并生成桥接方法来维持方法重写关系。
List
不过,类、字段和方法的泛型声明可以保留签名信息供反射读取。所以不能把类型擦除理解成所有泛型相关信息都消失。

图:泛型约束主要在编译时。
知识点详解:编译器检查过,不等于集合会在运行时逐个验类型
泛型先解决什么问题?
假设一个列表约定只放字符串。没有编译期约束时,加入数字很容易被允许,取出来的人还要自己检查和转换。
泛型把这类错误尽量提前:在正常的类型检查路径里,往 List
不过,它采用的实现方式,并不是为每一种元素类型都生成一套不同的集合类。
擦除后,哪些东西还在?
无界类型参数擦除为 Object;带上界的参数按规则擦除为相应上界。方法描述符和执行指令需要符合这种运行时表示。必要时,编译器补上类型转换。
但声明上的泛型签名,可以作为元数据保留。例如,一个字段被声明为 List
这两件事分别回答“字段是怎么声明的”与“这个对象目前放了什么”,不要混成一个问题。规则见 JLS 类型擦除与可具体化类型。
错误元素为什么可能到读取时才报错?
假设旧代码拿到的是原始类型 List,而不是带实参的 List
集合存储这个元素时,通常没有一份运行时的 String 实参用来逐个阻止它。后续代码按 String 读取该位置,编译器插入的转换才发现对象实际是 Integer,于是抛出 ClassCastException。
所以,失败位置可能在读取侧,污染来源却在更早的写入侧。只检查最后一行代码,不容易找到根因。

图:污染在写入,错误在读取。
为什么一些写法受限制?
类型变量 T 不是一个随时可用于运行时创建对象的具体类,因此不能想当然地直接 new T。类似地,不能对任意参数化类型做完全具体的 instanceof 检查。
如果运行时确实需要知道类型,通常要显式传入 Class、工厂或其他类型描述。至于多层泛型信息,还需要合适的类型表示方案,不能认为一个 Class 就能表达所有嵌套实参。
桥接方法也是编译器为擦除后的方法关系做的适配,不是开发者手工维护另一套业务方法。本题聚焦概念,不用 TS、Python 的泛型机制模拟 JVM 擦除。
面试官继续追问
所有转换警告都可以忽略吗?
不应该。先检查能不能调整接口,保留类型约束。确有受控转换时,把它限制在小范围内,说明不变量并测试,不要到处 suppress warning。
反射读到 List
不能。它读到的是声明信息,不是对容器实际内容逐一验证的结果。
面试速记卡
- 作用:泛型主要提前到编译期发现类型问题。
- 擦除:无界通常到 Object,有界按上界规则处理。
- 对象:不同实参通常不产生不同的集合运行时类。
- 污染:不安全写入可能延迟到读取转换时失败。
- 元数据:声明签名可保留,不等于对象内容已验证。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →