Sunday面试指南

Java 泛型为什么要类型擦除?List<String> 和 List<Integer> 在运行时是不同类型吗?

下面是一段教学用的模拟面试。

🧑‍💻 面试官: Java 泛型的类型擦除是什么意思?

🙋‍♂️ 我: 编译后泛型类型就没有了,都变成 Object。

🧑‍💻 面试官: 有上界的类型参数也一定变成 Object 吗?反射为什么有时还能读到泛型信息?

🙋‍♂️ 我: 可能还有部分声明信息。

🧑‍💻 面试官: 那把一个 Integer 混进 List,究竟在哪一步报错?

泛型没有“全部消失”,也不会给每个集合对象都装上元素类型检查。要分清编译约束、运行时表示和声明元数据。

面试速答(60 秒版)

Java 泛型主要在编译期检查类型,并通过类型擦除保持与既有运行时和代码的兼容。

擦除时,无界类型参数通常变成 Object;有界类型参数会按相应规则擦除为上界,不是全部都变成 Object。编译器还可能插入类型转换,并生成桥接方法来维持方法重写关系。

List 和 List 通常不会因为类型实参不同,就对应两个不同的集合运行时类。使用原始类型或不安全转换,可能把错误元素混进去,直到按 String 读取时才触发类型转换错误。

不过,类、字段和方法的泛型声明可以保留签名信息供反射读取。所以不能把类型擦除理解成所有泛型相关信息都消失。

泛型约束主要在编译时

图:泛型约束主要在编译时。

知识点详解:编译器检查过,不等于集合会在运行时逐个验类型

泛型先解决什么问题?

假设一个列表约定只放字符串。没有编译期约束时,加入数字很容易被允许,取出来的人还要自己检查和转换。

泛型把这类错误尽量提前:在正常的类型检查路径里,往 List 添加 Integer 会被编译器拒绝。使用者取值时,也不必在源码中反复手写转换。

不过,它采用的实现方式,并不是为每一种元素类型都生成一套不同的集合类。

擦除后,哪些东西还在?

无界类型参数擦除为 Object;带上界的参数按规则擦除为相应上界。方法描述符和执行指令需要符合这种运行时表示。必要时,编译器补上类型转换。

但声明上的泛型签名,可以作为元数据保留。例如,一个字段被声明为 List,反射有机会读取这份字段声明。这不代表拿到任意一个 ArrayList 对象,都能从对象自身可靠地判断它实际装的所有元素类型。

这两件事分别回答“字段是怎么声明的”与“这个对象目前放了什么”,不要混成一个问题。规则见 JLS 类型擦除与可具体化类型。

错误元素为什么可能到读取时才报错?

假设旧代码拿到的是原始类型 List,而不是带实参的 List。它加入一个数字,可能绕开正常的泛型检查路径,并产生编译警告。

集合存储这个元素时,通常没有一份运行时的 String 实参用来逐个阻止它。后续代码按 String 读取该位置,编译器插入的转换才发现对象实际是 Integer,于是抛出 ClassCastException。

所以,失败位置可能在读取侧,污染来源却在更早的写入侧。只检查最后一行代码,不容易找到根因。

污染在写入,错误在读取

图:污染在写入,错误在读取。

为什么一些写法受限制?

类型变量 T 不是一个随时可用于运行时创建对象的具体类,因此不能想当然地直接 new T。类似地,不能对任意参数化类型做完全具体的 instanceof 检查。

如果运行时确实需要知道类型,通常要显式传入 Class、工厂或其他类型描述。至于多层泛型信息,还需要合适的类型表示方案,不能认为一个 Class 就能表达所有嵌套实参。

桥接方法也是编译器为擦除后的方法关系做的适配,不是开发者手工维护另一套业务方法。本题聚焦概念,不用 TS、Python 的泛型机制模拟 JVM 擦除。

面试官继续追问

所有转换警告都可以忽略吗?

不应该。先检查能不能调整接口,保留类型约束。确有受控转换时,把它限制在小范围内,说明不变量并测试,不要到处 suppress warning。

反射读到 List,能证明当前列表没有数字吗?

不能。它读到的是声明信息,不是对容器实际内容逐一验证的结果。

面试速记卡

  • 作用:泛型主要提前到编译期发现类型问题。
  • 擦除:无界通常到 Object,有界按上界规则处理。
  • 对象:不同实参通常不产生不同的集合运行时类。
  • 污染:不安全写入可能延迟到读取转换时失败。
  • 元数据:声明签名可保留,不等于对象内容已验证。

公司面试真题

这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。

浏览公司面试真题 →
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历