Java泛型在运行时类型擦除导致List和List共享同一Class对象,即java.util.ArrayList.class;擦除后所有泛型实例均归一为原始类型ArrayList,故getClass()返回相同结果。

因为泛型信息在编译后被完全擦除,List<string></string> 和 List<integer></integer> 在运行时都退化为原始类型 List,对应同一个 Class 对象 —— 即 java.util.ArrayList.class(或具体实现类的 Class)。
擦除发生在编译期,JVM 看不到泛型
Java 泛型是“伪泛型”,设计目标是向后兼容。编译器只用泛型做静态检查,一旦通过,所有类型参数(如 String、Integer)就被移除,字节码中只剩原始类型(ArrayList)。所以:
-
new ArrayList<string>()</string>和new ArrayList<integer>()</integer>编译后都是new ArrayList() -
.getClass()返回的是运行时类对象,自然相同 - 调用
list1.getClass() == list2.getClass()输出 true
原始类型(Raw Type)就是擦除后的唯一载体
所谓“原始类型”,是指泛型类去掉所有类型参数后的形式。例如:
-
ArrayList<t></t>的原始类型是ArrayList -
Map<k v></k>的原始类型是Map - 无论
T是String、Boolean还是自定义类,擦除后都指向同一个Class
这也解释了为什么不能用泛型类型做 instanceof 判断,比如 list instanceof ArrayList<string></string> 是语法错误 —— 运行时根本不存在 ArrayList<string></string> 这个类型。
擦除规则决定了类型归一
类型擦除不是简单删掉尖括号,而是按边界规则替换类型参数:
- 无界泛型(
<t></t>)→ 替换为Object - 上界泛型(
<t extends number></t>)→ 替换为Number - 通配符
extends CharSequence>→ 擦除为CharSequence - 但类本身的结构(如
ArrayList)不因泛型不同而生成多个版本
因此,所有实例共享同一份字节码和同一个 Class 实例,这是 JVM 类加载机制决定的:一个类名对应一个 Class 对象,与泛型无关。
这带来便利,也埋下隐患
共用 Class 让旧代码无需修改就能使用泛型集合,但也导致:
- 无法在运行时获取泛型实际类型(需靠
TypeToken或反射绕过) - 可通过反射调用
add(Object)向List<integer></integer>插入字符串 - 泛型数组创建受限(
new ArrayList<string>[10]</string>编译失败),因擦除后类型不具体
本质上,Class 唯一性不是 bug,而是类型擦除机制的必然结果。

















