Java泛型采用类型擦除机制,编译时移除泛型参数,运行时仅保留Object或上界类型,导致无法在运行时获取泛型信息、不能使用instanceof判断具体参数化类型、不能new T()等。

泛型擦除就是编译时把 T、List 全部干掉,运行时只剩 Object 或边界类型
Java 的泛型不是“真泛型”,它只在编译期起作用。你写 List,编译器会检查你只往里加 String,但一编译完,字节码里就只剩 List——连 String 这个词都消失了。JVM 根本不知道你曾经声明过泛型。
- 无边界泛型(如
)→ 擦除为Object - 有上界泛型(如
)→ 擦除为Number - 通配符(如
? extends Comparable)→ 编译期校验后,运行时也擦成原始类型
这也就是为什么下面两行代码的 getClass() 结果完全一样:
ArrayListlist1 = new ArrayList<>(); ArrayList list2 = new ArrayList<>(); System.out.println(list1.getClass() == list2.getClass()); // true
为什么不能用 instanceof 判断 List?因为运行时根本不存在这个类型
你写 if (obj instanceof List,编译器直接报错:泛型类型不可用于 instanceof。这不是语法限制,而是语义不可能——擦除后只有 List.class,没有 List。
- 反射获取泛型信息只能靠
ParameterizedType,且仅对字段、方法签名、父类声明等“静态位置”有效 - 集合实例本身(如
new ArrayList)不携带泛型运行时信息() - 想在运行时区分不同泛型,必须手动传入
Class类型令牌(type token)
擦除导致的三大硬伤:不能 new T()、不能 static T[] 、桥接方法悄悄生成
这些不是“设计缺陷”,而是擦除机制的必然结果。你写的每行泛型代码,编译器都在背后补动作:
立即学习“Java免费学习笔记(深入)”;
-
new T()编译失败 → 因为擦除后T是Object,但你没法在运行时知道该 new 哪个具体子类 - 静态泛型字段或方法中无法引用
T→ 静态属于类,而泛型参数属于实例化时的类型变量,二者生命周期不匹配 - 继承泛型类/实现泛型接口时,编译器自动生成桥接方法(bridge method)→ 例如
Comparable的compareTo(Integer)会被补一个compareTo(Object),用来应付多态调用
桥接方法看不见、摸不着,但用反射查 getDeclaredMethods() 就能看到它们的存在。
绕不开的现实:擦除是为了兼容 Java 5 之前的全部生态
Java 没有重写 JVM 来支持“真泛型”,是因为已有海量非泛型代码(比如 List、Map)和类库必须继续跑。如果泛型在运行时保留类型信息,那 List 和老 List 就是两个不兼容的类型,整个 JDK 集合框架就得推倒重来。
所以,擦除不是偷懒,是权衡:用编译期严格检查 + 运行时零开销 + 100% 二进制兼容,换掉了运行时类型可见性。你在 IDE 里享受的类型提示、安全的 get() 返回值,全是编译器在擦除前帮你拦下来的;而你在反射、序列化、动态代理里踩的坑,几乎都源于擦除后那一片空白。










