Java泛型类型擦除
使用Java泛型,我们可以编写泛型程序,并且还可以在编译时提供更严格的类型检查,但是这些泛型类型仅保留在源代码级别。编译源代码时,所有通用类型参数都将被删除,此过程在Java通用中称为类型擦除。
类型擦除如何工作
Java中的类型擦除操作如下:
如果未指定显式绑定类型,则将泛型类型中的所有类型参数替换为它们的绑定类型,然后用Object替换泛型类型参数。因此,生成的字节码仅包含普通的类,接口和方法,所有通用参数均被实际类型替换。
必要时插入类型转换,以保持类型安全。
生成桥接方法以在扩展的泛型类型中保留多态。
通用类中的类型擦除
考虑以下具有泛型类型参数T的泛型类。
public class GenericClass<T> {
T obj;
GenericClass(T obj){
this.obj = obj;
}
public T getObj() {
return obj;
}
}
由于类型参数T是无界的,因此Java编译器将其替换为Object,并且在编译类之后看起来像
public class GenericClass {
Object obj;
GenericClass(Object obj){
this.obj = obj;
}
public Object getObj() {
return obj;
}
}
考虑另一个带有有限类型参数的泛型类。
public class GenericClass<T extends String> {
T obj;
GenericClass(T obj){
this.obj = obj;
}
public T getObj() {
return obj;
}
}
由于类型参数T是有界的,因此Java编译器会用绑定类String替换它,并且在编译类看起来像这样之后
public class GenericClass {
String obj;
GenericClass(String obj){
this.obj = obj;
}
public String getObj() {
return obj;
}
}
通用方法中的类型擦除
Java编译器还会擦除通用方法参数中的类型参数。考虑以下通用方法,该方法对传递的数组中传递的元素的出现次数进行计数。
public static <T> int count(T[] numberArray, T elem) {
int cnt = 0;
for (T e : numberArray){
if (e.equals(elem))
++cnt;
}
return cnt;
}
由于T是无界的,因此Java编译器将其替换为Object,并且编译后的方法看起来像
public static int count(Object[] numberArray, Object elem) {
int cnt = 0;
for (Object e : numberArray){
if (e.equals(elem))
++cnt;
}
return cnt;
}
类型擦除和桥接方法
有时,作为类型擦除过程的一部分,编译器会创建一种综合方法,称为桥接方法。考虑以下类,以查看Java中的bridge方法的示例。
public class GenClass<T> {
T obj;
public GenClass(T obj) {
this.obj = obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
然后将此GenClass扩展为另一个类,如下所示:
public class MyClass extends GenClass {
public MyClass(Integer data) {
super(data);
}
public void setObj(Integer data) {
System.out.println("MyClass.setData");
super.setObj(data);
}
}
这里的目的是重写子类中的父类setObj()方法。类型擦除后,GenClass和MyClass类变为
public class GenClass {
Object obj;
public GenClass(Object obj) {
this.obj = obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
public class MyClass extends GenClass {
public MyClass(Integer data) {
super(data);
}
public void setObj(Integer data) {
System.out.println("MyClass.setData");
super.setObj(data);
}
}
类型擦除后,方法签名不匹配。 GenClass方法变为setObj(Object obj),而MyClass方法变为setObj(Integer data)。因此,GenClass setObj方法不会覆盖MyClass setObj方法。
为了解决此问题并在类型擦除后保留通用类型的多态性,Java编译器生成了一个桥接方法,以确保子类型可以按预期工作。对于MyClass类,编译器为setObj()生成以下桥接方法。
public class MyClass extends GenClass {
public MyClass(Integer data) {
super(data);
}
// Bridge method generated by the compiler
public void setObj(Object data) {
setObj((Integer) data);
}
public void setObj(Integer data) {
System.out.println("MyClass.setData");
super.setObj(data);
}
}
如我们所见,bridge方法具有与GenClass的setObj()方法相同的方法签名,并且它委托给原始的setObj()方法。

