Java泛型擦除是编译器将类型参数替换为Object或上界类型的过程,运行时JVM无法区分List<String>和List<Integer>;无界T→Object,单上界T extends Number→Number,多上界→第一个上界;字节码中字段和方法签名被擦除,调用处插入checkcast,桥接方法保障多态。

Java 中的泛型擦除不是“丢失”,而是编译器主动移除泛型类型参数、替换为原始类型或边界类型的过程。它让泛型只在编译期起作用,运行时 JVM 看不到 List<String> 和 List<Integer> 的区别——它们都变成单纯的 List。
泛型擦除的核心规则
编译器按以下方式处理泛型类型参数:
- 无边界类型参数(如
<T>)被替换为Object - 有单个上界(如
<T extends Number>)则替换为该上界(Number) - 有多个上界(如
<T extends Comparable & Serializable>)则替换为第一个(Comparable) - 泛型方法的类型参数同样被擦除,仅保留在字节码的
Signature属性中,不参与 JVM 方法调用分发
字节码层面的真实表现
用 javap -c -s 查看编译后的字节码,能清晰看到擦除效果:
-
public class Box<T> { private T value; }编译后字段变为private Object value;(若未指定上界) - 方法签名从
get() : T变成get() : Object,返回值类型被擦除 - 调用处自动插入强制转换:比如
String s = box.get();实际字节码含checkcast java/lang/String - 桥接方法(bridge method)会被生成,用于支持子类泛型重写,例如
Box<String>继承自Box<Object>时需保持多态正确性
为什么运行时无法获取泛型类型?
因为 JVM 类加载机制根本不解析泛型信息:
立即学习“Java免费学习笔记(深入)”;
-
list.getClass()对List<String>和List<Integer>都返回同一个Class<?>实例(即ArrayList.class) -
instanceof List<String>是语法错误——JVM 不认识带泛型的类型字面量 - 反射 API 中
Field.getGenericType()可拿到ParameterizedType,但这是编译器写入的元数据,不是运行时动态存在的类型对象 - 数组创建限制(
new T[10]编译失败)正是因为运行时无法确定T的具体 class
擦除带来的典型限制与应对思路
这些限制不是 bug,而是擦除机制的自然结果:
- 不能基于泛型类型做重载:两个方法
<T> void f(List<T>)和<U> void f(List<U>)擦除后都是f(List),编译报错 - 静态上下文中不可用类型参数:静态方法/字段不能引用类的泛型参数
T,因静态属于类而非实例,而泛型信息在实例化时已被擦除 - 泛型数组禁止:
new ArrayList<String>[10]合法,但new T[10]非法——因T在运行时不存在 - 运行时类型安全靠编译器“代劳”:所有泛型集合的
get()返回值,在字节码里都附带checkcast指令,异常发生在取值时而非存值时


















