java泛型擦除是编译器将类型参数替换为object或上界类型的过程,运行时jvm无法区分list和list;无界t→object,单上界t extends number→number,多上界→第一个上界;字节码中字段和方法签名被擦除,调用处插入checkcast,桥接方法保障多态。

Java 中的泛型擦除不是“丢失”,而是编译器主动移除泛型类型参数、替换为原始类型或边界类型的过程。它让泛型只在编译期起作用,运行时 JVM 看不到 List<string></string> 和 List<integer></integer> 的区别——它们都变成单纯的 List。
泛型擦除的核心规则
编译器按以下方式处理泛型类型参数:
- 无边界类型参数(如
<t></t>)被替换为Object - 有单个上界(如
<t extends number></t>)则替换为该上界(Number) - 有多个上界(如
<t extends comparable serializable></t>)则替换为第一个(Comparable) - 泛型方法的类型参数同样被擦除,仅保留在字节码的
Signature属性中,不参与 JVM 方法调用分发
字节码层面的真实表现
用 javap -c -s 查看编译后的字节码,能清晰看到擦除效果:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
-
public class Box<t> { private T value; }</t>编译后字段变为private Object value;(若未指定上界) - 方法签名从
get() : T变成get() : Object,返回值类型被擦除 - 调用处自动插入强制转换:比如
String s = box.get();实际字节码含checkcast java/lang/String - 桥接方法(bridge method)会被生成,用于支持子类泛型重写,例如
Box<string></string>继承自Box<object></object>时需保持多态正确性
为什么运行时无法获取泛型类型?
因为 JVM 类加载机制根本不解析泛型信息:
-
list.getClass()对List<string></string>和List<integer></integer>都返回同一个Class>实例(即ArrayList.class) -
instanceof List<string></string>是语法错误——JVM 不认识带泛型的类型字面量 - 反射 API 中
Field.getGenericType()可拿到ParameterizedType,但这是编译器写入的元数据,不是运行时动态存在的类型对象 - 数组创建限制(
new T[10]编译失败)正是因为运行时无法确定T的具体 class
擦除带来的典型限制与应对思路
这些限制不是 bug,而是擦除机制的自然结果:
- 不能基于泛型类型做重载:两个方法
<t> void f(List<t>)</t></t>和<u> void f(List<u>)</u></u>擦除后都是f(List),编译报错 - 静态上下文中不可用类型参数:静态方法/字段不能引用类的泛型参数
T,因静态属于类而非实例,而泛型信息在实例化时已被擦除 - 泛型数组禁止:
new ArrayList<string>[10]</string>合法,但new T[10]非法——因T在运行时不存在 - 运行时类型安全靠编译器“代劳”:所有泛型集合的
get()返回值,在字节码里都附带checkcast指令,异常发生在取值时而非存值时
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










