Java中的泛型擦除(Generics Type Erasure)是Java编译器应用的一个过程,用来允许泛型代码与在Java引入泛型之前(Java 5之前)编写的遗留代码兼容。泛型擦除的主要作用是在编译时期检查泛型的类型安全,并提供泛型的类型信息,但在编译后的字节码中去除所有泛型类型信息。这意味着泛型类和方法在字节码层面上将只使用原生类型(Raw Types)。
泛型擦除的具体行为包括:
类型擦除:泛型类型变量被擦除替换为第一个边界的类型(如果存在边界),或者Object
(如果没有指定边界)。例如,List<T>
在运行时会变成List
,List<String>
也会变成List
。
类型检查:编译器在编译时期会使用泛型信息进行类型检查,确保代码中对泛型类型的使用是类型安全的。
桥接方法:如果泛型类型擦除导致方法签名的变化,编译器会生成桥接方法(Bridge Methods)来保持多态性。这是Java编译器生成的额外方法,用来保证子类能正确地覆盖父类或接口中的泛型方法。
类型转换:在必要的地方插入类型转换,以保证类型安全的实例化。由于运行时类型信息已经被擦除,所以需要在代码中的特定位置插入强制类型转换,以维持程序的行为。
举个例子,下面的泛型类在运行时会受到类型擦除的影响:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
// 使用泛型
Box<String> stringBox = new Box<>();
stringBox.set("Hello World");
String value = stringBox.get();
在编译时,Box<T>
中的 T
被擦除,并替换为 Object
。所以,即使你创建了一个 Box<String>
,在运行时 Box
类中的所有 T
都会被当作 Object
类型处理。因此,编译后的代码会类似于下面这样:
public class Box {
private Object t;
public void set(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
// 使用擦除后的类型
Box stringBox = new Box();
stringBox.set("Hello World");
String value = (String) stringBox.get(); // 显式的类型转换
注意,泛型擦除可能导致某些类型信息在编译后不可用,这意味着你在运行时无法获取泛型的类型参数。例如,你无法直接检查一个集合是否是 List<String>
或 List<Integer>
,因为在运行时它们都只是 List
。这就是为什么有时你需要通过其他方式来传递类型信息,如通过方法参数(Class<T> clz
)或使用反射技术。