泛型擦除发生在编译期,由javac完成而非jvm;无界类型参数擦除为object,有界则取首个上界;擦除不可逆,导致instanceof、new t()等运行时操作失效,但部分元数据仍保留在class文件中供ide和框架使用。

泛型擦除发生在编译期,不是运行时
Java 泛型的“类型信息消失”不是 JVM 做的,而是 javac 编译器在生成字节码前就完成了擦除。你写的 List<string></string> 和 List<integer></integer>,在编译后都变成裸的 List 类型——它们的 getClass() 返回值完全相同,字节码里也找不到任何 String 或 Integer 的痕迹。
关键点在于:擦除是“不可逆”的预处理步骤,不是运行时丢弃。所以反射、instanceof、new T() 这些依赖运行时类型的操作,全部失效。
擦除规则很机械:看有没有上界,没上界就变 Object
编译器按固定规则替换类型参数,不推理、不猜测:
-
<t></t>(无界)→ 擦除为Object -
<t extends number></t>→ 擦除为Number -
<t extends comparable serializable></t>→ 擦除为Comparable(只取第一个接口)
例如这个类:
立即学习“Java免费学习笔记(深入)”;
public class Box<T extends Runnable> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
擦除后等效于:
public class Box {
private Runnable value;
public void set(Runnable value) { this.value = value; }
public Runnable get() { return value; }
}
为什么编译期还能报错?因为擦除前先检查
你以为 list.add(123) 报错是因为“运行时发现类型不对”,其实不是——它根本过不了编译。流程是:先做泛型语义检查 → 再擦除 → 最后生成字节码。
所以这些行为你得清楚:
-
List<string> list = new ArrayList(); list.add(42);</string>→ 编译失败,add方法签名被擦除为add(Object),但编译器在擦除前已判定42不符合String约束 -
String s = list.get(0);→ 编译器自动补上(String)强转,字节码里能看到checkcast指令 - 用反射调
add就能绕过检查,往List<string></string>里塞Integer—— 因为反射跳过了编译期校验
擦除带来的硬限制,躲不开也改不了
这不是 bug,是设计契约。所有泛型相关限制都源于擦除不可逆:
- 不能写
if (list instanceof List<string>)</string>→ 编译报错,语法不合法 - 不能声明
static <t> T getValue()</t>中的T→ 静态上下文看不到类型参数 - 不能
new T[]或new T()→ 运行时不知道T是什么类 - 泛型数组必须靠
Array.newInstance(clazz, size)+ 强制转型,且会带@SuppressWarnings("unchecked")
真正容易被忽略的是:泛型信息虽被擦除,但部分元数据(如 Signature、LocalVariableTypeTable)仍保留在 class 文件里——仅用于 IDE 提示、框架反射读取(如 Spring、Jackson),JVM 自身完全无视它们。










