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()方法。