type erasure & reified generic
Java的泛型不同于C++的模板:Java泛型是"type erasure",C++模板是"reified generic"。
- type erasure:泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。
- reified generic:泛型类型存在于编译和运行期间,编译器自动为每一种泛型类型生成类型代码并编译进二进制码中。
为什么Java是type erasure
这是由于泛型是后来(SE5)才加入到Java语言特性的,Java让编译器擦除掉关于泛型类型的信息,这样使得Java可以向后兼容之前没有使用泛型的类库和代码,因为在字节码层面是没有泛型概念的。
type erasure的本质
泛型(T) --> 编译器(type erasure) --> 原始类型(T被Object替换)
泛型(? extends XXX) --> 编译器(type erasure) --> 原始类型(T被XXX替换)
原始类型指被编译器擦除了泛型信息后,类型变量在字节码中的具体类型。
假如,我们定义一个泛型类Generic
是这样的:
class Generic<T> {
private T obj;
public Generic(T o) {
obj = o;
}
public T getObj() {
return obj;
}
}
那么,Java编译后的字节码中Generic
相当于这样的:
class Generic {
private Object obj;
public Generic(Object o) {
obj = o;
}
public Object getObj() {
return obj;
}
}
假如,我们使用Generic
类是这样的:
public static void main(String[] args) {
Generic<String> generic = new Generic<String>("hehe...");
String str = generic.getObj();
}
那么,Java编译后的字节码中相当于这样的:
public static void main(String[] args) {
Generic generic = new Generic("hehe...");
String str = (String) generic.getObj();
}
所以,所有Generic
的泛型类型实质是同一个类:
public static void main(String[] args) {
Generic<Integer> a = new Generic<Integer>(111);
Generic<String> b = new Generic<String>("bbb");
System.out.println("a'class: " + a.getClass().getName());
System.out.println("b'class: " + b.getClass().getName());
System.out.println("G'class: " + Generic.class.getName());
System.out.println("a'class == b'class == G'class: " + (a.getClass() == b.getClass() && b.getClass() == Generic.class));
}
上述代码执行结果:
a'class: generic.Generic
b'class: generic.Generic
G'class: generic.Generic
a'class == b'class == G'class: true
小结:Java的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。
type erasure导致泛型的局限性
类型擦除降低了泛型的泛化性,使得某些重要的上下文环境中不能使用泛型类型,具有一定的局限性。
运行时隐含类型转换的开销
使用泛型时,Java编译器自动帮我们生成了类型转换的代码,这相对于C++模板来说无疑带来了额外的性能开销。
类型参数不能实例化
T obj = new T(); // compile error
T[] objs = new T[10]; // compile error
Generic<String> generic = new Generic<String>[10]; // compile error
类型参数不能进行类型查询(类型查询在运行时,运行时类型参数已被擦除)
Generic<Integer> a = new Generic<Integer>(111);
if(a instanceof Generic<String>)// compile error
if(a instanceof Generic<T>) // compile error
if(a instanceof Generic) // 仅测试了a是否是Generic,忽略了类型参数
不能在静态域和静态方法中引用类型变量
class Generic<T> {
private static T obj;// compile error
public static T func(){...}// compile error
}
因为所有泛型类最终映射到同一个原始类型类,而静态属性是类级别的,类和实例共同拥有它的一份存储,因此一份存储无法安放多个类型的属性。静态方法也是如此。
重载方法签名冲突:
public boolean equals(T obj) // compile error
public boolean equals(T obj)
被擦除类型后变为public boolean equals(Object obj)
,与根类Object的public boolean equals(Object obj)
签名一样,而两者均不能覆盖对方,导致编译期名称冲突。
一个类不能实现同一个泛型接口的两种变体:
interface IFace<T>() {}
class FaceImpParent implements IFace<String> {}
class FaceImpChild extends FaceImpParent implements IFace<Integer> {} // compile error
原因是IFace<String>
和IFace<Integer>
在擦除类型后是同一个接口,一个类不能实现两次同一个接口。
泛型类不能扩展java.lang.Throwable
class GenericException <T> extends Exception {} // compile error