Java泛型擦除是编译器为兼容旧代码的主动设计,编译期移除类型参数,运行时仅保留原始类型和上界,导致无法在运行时获取泛型实际类型,故不能用instanceof判断、不能new T[]、反射也受限于泛型信息可见范围。

Java 泛型擦除不是“bug”,而是编译器为兼容老代码做的主动设计:它把泛型类型参数在编译期移除,运行时只留下原始类型(如 List 和 List 都变成 List),所有泛型变量统一擦除为 Object(无边界)或其上界(如 T extends Number 擦除为 Number)。好处是旧代码不用改就能跑;代价是运行时无法知道真实类型,很多你以为能做的事,其实编译就拦住了。
为什么不能用 instanceof 判断泛型类型
因为擦除后类型信息已不存在。下面这行代码直接编译失败:
if (obj instanceof List
JVM 看到的只是 List,不区分里面是 String 还是 Integer。类似地,反射也拿不到泛型实参:
-
list.getClass().getTypeParameters()返回空数组 -
list.getClass().getGenericSuperclass()可能返回ArrayList,但E是占位符,不是具体类型
为什么不能 new T[] 或直接强转泛型数组
擦除导致 JVM 在运行时不知道 T 是什么,所以 new T[10] 编译不过。常见替代写法是传入 Class 或用 Object[] 再转型:
- 错误:
T[] arr = new T[10];→ 编译报错 - 可行:
@SuppressWarnings("unchecked") T[] arr = (T[]) new Object[10];(需加注解,且不安全) - 更稳:
T[] arr = (T[]) Array.newInstance(componentType, size);
通配符怎么选:? extends 和 ? super 的实际意义
擦除让“读”和“写”的安全性分离,PECS 原则(Producer-Extends, Consumer-Super)就是为此而生:
-
List extends Number>:只能读,不能 add(除 null),因为你知道取出来一定是Number或其子类,但不知道具体是哪个,不敢往里塞 -
List super Integer>:可以 addInteger及其子类,但读出来只能当Object用,因为下界不确定上限 - 别写
List>后直接强转——它等价于List,但编译器禁止你往里 add 任何非 null 值,强行绕过会埋雷
反射与泛型配合时的典型陷阱
想通过反射获取字段或方法的泛型类型?得用 getGenericType() 而非 getType():
-
field.getType()返回List.class(原始类型) -
field.getGenericType()返回ParameterizedType,可调用getActualTypeArguments()拿到Type数组 - 但注意:仅对**成员变量、方法参数、返回值**有效;局部变量、泛型类本身、方法体内的泛型实例,反射完全不可见
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











