摘要:泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。的本质泛型编译器原始类型被替换泛型编译器原始类型被替换原始类型指被编译器擦除了泛型信息后,类型变量在字节码中的具体类型。
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{ 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) { Genericgeneric = new Generic ("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) { Generica = new Generic (111); Generic b = new Generic ("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类型参数不能进行类型查询(类型查询在运行时,运行时类型参数已被擦除)generic = new Generic [10]; // compile error
Generic不能在静态域和静态方法中引用类型变量a = new Generic (111); if(a instanceof Generic )// compile error if(a instanceof Generic ) // compile error if(a instanceof Generic) // 仅测试了a是否是Generic,忽略了类型参数
class Generic{ 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() {} class FaceImpParent implements IFace {} class FaceImpChild extends FaceImpParent implements IFace {} // compile error
原因是IFace
class GenericExceptionextends Exception {} // compile error
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/66827.html
摘要:在之后提供了泛型,允许在定义类的时候使用类型作为参数。泛型广泛应用于各类集合中。本文对其以及其用法进行介绍。报错如下原因是类型擦除机制,在编译成文件时候,编译器并未把和类型信息编译进去。通配符和无界通配符无界通配符可接收任何类型。 在JDK5之后java提供了泛型(Java Genertics),允许在定义类的时候使用类型作为参数。泛型广泛应用于各类集合中。本文对其以及其用法进行介绍。...
摘要:首先,我们来按照泛型的标准重新设计一下类。注意参数为而不是泛型。利用形式的通配符,可以实现泛型的向上转型,来看例子。需要注意的是,无法从这样类型的中取出数据。showImg(https://user-gold-cdn.xitu.io/2019/5/17/16ac3bf3eb16160c); 00、故事的起源 二哥,要不我上大学的时候也学习编程吧?有一天,三妹突发奇想地问我。 你确定要做一名...
摘要:问题在遇到有同学反馈了个问题第一眼的感觉应该是泛型擦除和类型推断导致的但当我尝试去彻底解释这个问题的时候才发现关键原因是如果在调用方法时有那么方法返回的是定义中返回类型经过擦除后的结果具体问题是这个样子的错误不兼容的类型无法转换为猜测 问题 在 v2 遇到有同学反馈了个问题, 第一眼的感觉应该是泛型擦除(Type Erasure)和类型推断(Type Inference)导致的. 但当...
阅读 3895·2021-11-23 10:09
阅读 1287·2021-11-23 09:51
阅读 2910·2021-11-23 09:51
阅读 1530·2021-09-07 09:59
阅读 2286·2019-08-30 15:55
阅读 2217·2019-08-30 15:55
阅读 2893·2019-08-30 15:52
阅读 2501·2019-08-26 17:04