Java 泛型类型擦除:编译器背后的秘密
·
泛型看似强大的类型机制,其实只存在于编译期。
在运行时,它们都被“擦除”成原始类型(Raw Type)。
本文将深入讲解泛型类型擦除的原理、表现与影响。
一、什么是类型擦除(Type Erasure)
类型擦除 是 Java 实现泛型的核心机制。
泛型信息只存在于 编译阶段,编译完成后,所有类型参数都会被擦除。
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true、
输出:
true
💡 编译器认为两者的运行时类型完全相同:
ArrayList。
二、为什么 Java 要类型擦除?
历史原因:为了与 Java 1.4 以前的代码兼容。
JVM 层面并没有真正的泛型类型支持,因此泛型由编译器“伪装”实现。
编译器行为:
- 编译时检查泛型类型安全;
- 生成字节码时擦除泛型信息;
- 自动插入强制类型转换。
三、类型擦除的过程
1️⃣ 无边界类型擦除
class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
擦除后变成:
class Box {
private Object value;
public void set(Object value) { this.value = value; }
public Object get() { return value; }
}
2️⃣ 有上界类型擦除
class Calculator<T extends Number> {
public void show(T t) {
System.out.println(t.doubleValue());
}
}
擦除后:
class Calculator {
public void show(Number t) {
System.out.println(t.doubleValue());
}
}
四、桥方法(Bridge Method)
泛型擦除可能导致 方法签名冲突。
JVM 通过自动生成“桥方法”保证多态正确性。
示例:
class Parent<T> {
T getValue() { return null; }
}
class Child extends Parent<String> {
@Override
String getValue() { return "Child"; }
}
编译后,Child 类会生成一个桥方法:
// 自动生成
Object getValue() { return getValue(); }
⚙️ 这就是为什么泛型方法仍然能正确覆写的原因。
五、泛型擦除导致的限制
| 限制 | 原因 |
|---|---|
| ❌ 无法创建泛型数组 | new T[10] 在运行时无法确定类型 |
| ❌ 无法使用 instanceof 检查泛型类型 | if (obj instanceof List<String>) 无法通过编译 |
| ❌ 泛型类型无法直接获取 Class | T.class 无法使用 |
| ❌ 静态成员无法使用泛型类型参数 | 泛型类型参数属于实例级别 |
六、通过反射获取泛型信息
虽然擦除了类型信息,但反射仍可读取部分泛型定义(存在于 class 元数据中)。
Field field = User.class.getDeclaredField("list");
Type type = field.getGenericType();
System.out.println(type);
输出:
java.util.List<java.lang.String>
七、类型擦除引发的陷阱
⚠️ 泛型与重载
void test(List<String> list) {}
void test(List<Integer> list) {} // 编译错误:擦除后方法签名相同
擦除后两者都变为:
void test(List list)
八、类型擦除与原始类型(Raw Type)
List raw = new ArrayList(); // 原始类型(不安全)
raw.add("A");
raw.add(1);
原始类型跳过编译期检查,可能导致运行时异常:
for (String s : raw) { // ClassCastException
System.out.println(s);
}
九、JVM 层面:泛型的“假象”
泛型在 JVM 层面并不存在独立类型,JVM 只识别:
Ljava/util/List;
而不是:
Ljava/util/List<Ljava/lang/String;>;
泛型是 编译器的语法糖,运行时全部擦除为原始类型。
🔚 十、总结
| 概念 | 含义 |
|---|---|
| 类型擦除 | 编译阶段删除泛型信息 |
| 上界擦除 | 转换为限定类型,如 T extends Number → Number |
| 桥方法 | 为保持多态性自动生成的桥接函数 |
| 限制 | 无法实例化泛型数组、无法检查泛型类型 |
更多推荐



所有评论(0)