类型擦除是java编译器在编译期主动将泛型替换为原始类型(无界擦除为object,有界擦除为上界类型),并插入类型检查与强制转换以保障类型安全的机制。

类型擦除不是运行时的“丢失”,而是编译期的主动处理——它让泛型只在编译阶段起作用,同时为类型安全铺路。理解这一点,就能看清为什么 Java 能在不改 JVM 的前提下引入泛型。
编译期检查依赖擦除前的类型信息
编译器在擦除发生前,会完整解析源码中的泛型声明(如 List<string></string>),据此做两件事:
- 验证所有操作是否符合类型约束:比如不允许向
List<string></string>添加Integer - 推导并插入隐式强制转换:比如调用
list.get(0)时,自动补上(String)转换
这些检查和插入动作,全靠擦除前的泛型结构支撑。一旦擦除完成(生成 .class 文件),这些信息就不再参与运行逻辑。
擦除本身是编译器的“翻译动作”,不是缺陷
类型擦除不是编译器“忘了保留类型”,而是有意识地把泛型代码“翻译”成 JVM 能懂的原始类型代码。例如:
Listlist.add("ok");
String s = list.get(0);
编译后等效于:
list.add("ok");
String s = (String) list.get(0);
这个过程里,擦除是手段,类型安全是目的——编译器用擦除换来了向后兼容,又用插入转换保住了类型正确性。
不能绕过擦除做运行时泛型判断,但可以辅助编译检查
正因为擦除发生在编译末期,以下写法在编译阶段就会被拦住:
-
if (list instanceof List<string>)</string>→ 编译错误,语法不合法 -
new ArrayList<string>().getClass() == new ArrayList<integer>().getClass()</integer></string>→ 编译通过,运行返回true
这说明:JVM 层面没有泛型概念,所以任何试图在运行时靠泛型做分支或实例化(如 new T())的操作,都会在编译期被拒绝或报错——这本身就是编译期检查在起作用。
泛型边界(extends)直接影响擦除后的替换结果
擦除不是一律换成 Object,而是看有没有上界:
-
<t></t>擦除为Object -
<t extends number></t>擦除为Number -
<t extends comparable>&Serializable></t>擦除为第一个接口Comparable
这个替换规则决定了编译器插入什么类型的转换、生成什么桥接方法,也直接影响你在方法签名中能写出哪些合法的泛型约束——它不是黑盒,而是可预测、可追踪的编译逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











