跳到主要内容

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;
}
}

编译后,泛型参数 TE 都被替换为 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 或反射等机制。

桥接方法是编译器自动生成的,目的是保持多态性,开发者无需手动编写。