Java泛型是编译期特性,运行时类型擦除导致List<String>和List<Integer>共享同一Class对象,getClass()比较返回true,反射无法获取实例泛型参数,仅字段等编译期保留位置可读取泛型信息。

Java 的泛型是编译期特性,运行时会进行类型擦除,所以 List<String> 和 List<Integer> 在运行期确实共享同一个 Class 对象 —— 即 java.util.ArrayList(或你实际使用的具体实现类),泛型参数信息被完全抹除。
用 getClass() 直接比较两个泛型列表的运行时类
最直观的验证方式:创建两个不同泛型类型的 ArrayList 实例,调用 getClass() 并比较是否相等。
- 代码示例:
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // 输出 true
System.out.println(list1.getClass().getName()); // 输出 java.util.ArrayList
结果为 true,说明它们在运行时确实是同一个 Class。
通过反射获取泛型声明(只能看到编译期保留的信息)
虽然运行时类型被擦除,但某些地方(如字段、方法签名、类定义)的泛型信息仍保留在字节码中,可通过反射读取。但对普通变量或局部集合实例,无法反推出其泛型参数。
立即学习“Java免费学习笔记(深入)”;
- 例如:定义一个成员字段
private List<String> data;,可用Field.getGenericType()获取ParameterizedType,从而得到String; - 但对
new ArrayList<Integer>()这样的局部对象,getClass().getTypeParameters()返回空数组,getGenericSuperclass()也只返回原始类型AbstractList<E>,其中E已无实际绑定信息。
尝试强制转型来感受擦除的实际影响
由于运行时无泛型约束,你可以把 List<String> 强转成 List<Integer>(编译警告但能运行),再往里加整数 —— 运行时不报错,但取值时才会抛 ClassCastException。
- 示例:
strList.add("hello");
// 编译通过,运行时无检查
List<Integer> intList = (List<Integer>) (List) strList;
intList.add(123); // OK
String s = strList.get(0); // OK
Integer i = strList.get(1); // 运行时 ClassCastException
这正体现了擦除后类型安全由编译器保障,运行时只剩原始类型。
打印 class 的 TypeVariable 或泛型父类信息
查看 ArrayList.class 自身的泛型结构,可确认它声明为 ArrayList<E>,但不包含具体实参:
-
ArrayList.class.getTypeParameters()返回一个TypeVariable数组,名字是E,不代表任何具体类型; -
ArrayList.class.getGenericSuperclass()返回AbstractList<E>,其中E是未绑定的类型变量; - 没有途径从
list1.getClass()得到 “这个 ArrayList 当初是 List<String>” 这类信息。


















