java泛型擦除是编译期系统性替换:类型参数按上界替换成object或边界类型,引用降级为原始类型,编译器插入强制转换,静态上下文清空,运行时无泛型信息。

泛型类型擦除不是“删代码”,而是编译器在生成字节码前,把所有泛型参数替换成具体类型(Object 或上界),并插入隐式强转——运行时 JVM 完全看不到 <string></string> 这类信息。攻克它,关键不在背规则,而在亲手验证“擦除后什么变了、什么不能做、为什么不能”。
盯住擦除的三处真实表现
不看文档,直接写这几行代码跑一遍,印象立刻加深:
-
getClass() 返回原始类型:new ArrayList
().getClass() == new ArrayList ().getClass() → true。两者字节码完全一样,都是 ArrayList。 -
instanceof 只能判原始类型:List
list = new ArrayList(); list instanceof List → 合法;list instanceof List → 编译报错。因为运行时没有 List<string></string>这个类型。 - 泛型数组创建被禁止:T[] arr = new T[5]; → 编译失败;必须写成 T[] arr = (T[]) new Object[5]; → 警告但能过。这是因为 new 操作需要确切的运行时类型,而 T 已被擦成 Object。
理解擦除背后的两个硬约束
很多“不能做”其实都源于同一个事实:JVM 不认识泛型,所有泛型实例共享同一份字节码。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
-
静态成员不能用泛型参数:static void print(T t) { } 是非法的。因为 static 属于类,而 T 属于实例;擦除后所有 Box
、Box 都是同一个 Box 类,T 早已不存在。 - 不能 new T() 或 new T[]:构造器调用和数组创建都依赖运行时类型信息,而擦除后只剩 Object,编译器无法确认 T 是否有无参构造、是否是引用类型。
通配符与擦除的联动逻辑
PECS(Producer Extends, Consumer Super)不是口诀,是擦除限制下的安全读写策略:
-
? extends Number:只能读,不能 add(除了 null)。因为擦除后底层是 Number,你 add(1) 看似合理,但实际可能指向 ArrayList
,而它不允许存 Float —— 编译器为保安全,一律禁止写。 - ? super Integer:只能写,读出来是 Object。因为擦除后底层是 Object 或其父类,add(Integer) 总是安全的;但 get() 返回的是上界类型(如 Object),无法保证是 Integer。
反射中如何应对擦除
运行时拿不到泛型实参,但可以通过方法/字段的声明位置反推:
- 用
Method.getGenericReturnType()或Field.getGenericType()获取带泛型的 Type 对象(如 ParameterizedType)。 - 对泛型方法,需配合
Method.getActualTypeArguments()提取实参(注意:仅对方法定义中的泛型有效,对调用时传入的泛型无效)。 - 常见坑:
list.getClass().getGenericSuperclass()可拿到 ArrayList的 E,但 new ArrayList<string>().getClass()...</string>拿不到 String —— 因为擦除发生在实例化之后。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










