Java 泛型的类型擦除与桥接方法
类型擦除(Type Erasure)
在 Java 中,泛型只在编译阶段进行类型检查。编译通过后,所有的泛型信息都会在进入 JVM 阶段之前被替换掉,这种机制称为类型擦除(Type Erasure)。其特点如下。
泛型类型在编译后被替换为 Object
或其上限类型。
类型擦除确保了与非泛型代码的兼容性。
以下是类型擦除的示例:
public class ErasureTest<T> {
private T value;
public ErasureTest(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
编译后,泛型参数 T
被替换为 Object
:
public class ErasureTest {
private Object value;
public ErasureTest(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
为了验证类型擦除的效果,我使用反射查看编译后的字节码:
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) {
ErasureTest<String> erasureTest = new ErasureTest<>("测试");
Class<?> clazz = erasureTest.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + " 的类型是 " + field.getType().getSimpleName());
}
}
}
输出结果如下:
value 的类型是 Object
可以看到,泛型类型 T
被替换为了 Object
。
上限类型替换
当泛型参数指定了上限时,类型擦除会将泛型替换为上限类型。如下代码所示:
public class ErasureTest<T extends Number> {
private T value;
public ErasureTest(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public <E extends Number> E getNumber(E number) {
return number;
}
}
编译后,泛型参数 T
和 E
都被替换为 Number
:
public class ErasureTest {
private Number value;
public ErasureTest(Number value) {
this.value = value;
}
public Number getValue() {
return value;
}
public void setValue(Number value) {
this.value = value;
}
public Number getNumber(Number number) {
return number;
}
}
桥接方法(Bridge Method)
当一个泛型接口被一个非泛型类实现时,由于类型擦除,可能导致方法签名不一致。为了保证多态性和方法签名的一致性,编译器会在编译时自动生成桥接方法(Bridge Method)。
定义一个泛型接口:
public interface TestInterface<T> {
T test(T value);
}
非泛型实现类:
public class TestImpl implements TestInterface<String> {
@Override
public String test(String value) {
return value;
}
}
由于类型擦除,接口的方法签名变为 Object test(Object value)
,而实现类的方法签名是 String test(String value)
。为了保持方法签名一致,编译器生成了一个桥接方法:
public Object test(Object value) {
return test((String) value);
}
这样,在运行时调用接口方法时,能够正确地调用实现类的方法。
我使用反射查看生成的桥接方法:
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Class<TestImpl> clazz = TestImpl.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + " 的返回类型是 " + method.getReturnType().getSimpleName());
}
}
}
输出结果如下:
test 的返回类型是 String
test 的返回类型是 Object
可以看到,编译器生成了一个 test(Object value)
的桥接方法。
注意事项
类型擦除会导致类型信息丢失,无法在运行时获取泛型的实际类型。
如果需要保留泛型类型信息,可以使用 TypeToken
或反射等机制。
桥接方法是编译器自动生成的,目的是保持多态性,开发者无需手动编写。