资讯专栏INFORMATION COLUMN

Java泛型: 类型擦除(type erasure)

nidaye / 3007人阅读

摘要:泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。的本质泛型编译器原始类型被替换泛型编译器原始类型被替换原始类型指被编译器擦除了泛型信息后,类型变量在字节码中的具体类型。

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) {
    Generic generic = 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) {
    Generic a = 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

原因是IFaceIFace在擦除类型后是同一个接口,一个类不能实现两次同一个接口。

泛型类不能扩展java.lang.Throwable
    class GenericException  extends Exception {} // compile error

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66827.html

相关文章

  • java类型擦除(java type erasure mechanism)

    摘要:在之后提供了泛型,允许在定义类的时候使用类型作为参数。泛型广泛应用于各类集合中。本文对其以及其用法进行介绍。报错如下原因是类型擦除机制,在编译成文件时候,编译器并未把和类型信息编译进去。通配符和无界通配符无界通配符可接收任何类型。 在JDK5之后java提供了泛型(Java Genertics),允许在定义类的时候使用类型作为参数。泛型广泛应用于各类集合中。本文对其以及其用法进行介绍。...

    nodejh 评论0 收藏0
  • 教妹学 Java:晦涩难懂的泛型

    摘要:首先,我们来按照泛型的标准重新设计一下类。注意参数为而不是泛型。利用形式的通配符,可以实现泛型的向上转型,来看例子。需要注意的是,无法从这样类型的中取出数据。showImg(https://user-gold-cdn.xitu.io/2019/5/17/16ac3bf3eb16160c); 00、故事的起源 二哥,要不我上大学的时候也学习编程吧?有一天,三妹突发奇想地问我。 你确定要做一名...

    Crazy_Coder 评论0 收藏0
  • Java泛型

    摘要:泛型的参数只可以代表类,不能代表个别对象。生成的字节代码中不包含类型信息例如编译后看到的只是静态变量是被泛型类的所有实例所共享的。对于声明为的类,访问其中的静态变量的方法仍然是。不管是通过还是创建的对象,都是共享一个静态变量。 Java泛型的参数只可以代表类,不能代表个别对象。 由于Java泛型的类型参数之实际类型在编译时会被消除,所以无法在运行时得知其类型参数的类型。 In Jav...

    hankkin 评论0 收藏0
  • Java泛型总结

    摘要:静态变量是被泛型类的所有实例所共享的。对于这个类型系统,有如下的一些规则相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。在代码中避免泛型类和原始类型的混用。参考泛型类型擦除 Java泛型总结 Java泛型是JDK5引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用的时候使用具体的类型来替换。泛型最主要的应用是在JDK5...

    CoreDump 评论0 收藏0
  • Unchecked Conversion 导致的 Java 方法返回类型变更

    摘要:问题在遇到有同学反馈了个问题第一眼的感觉应该是泛型擦除和类型推断导致的但当我尝试去彻底解释这个问题的时候才发现关键原因是如果在调用方法时有那么方法返回的是定义中返回类型经过擦除后的结果具体问题是这个样子的错误不兼容的类型无法转换为猜测 问题 在 v2 遇到有同学反馈了个问题, 第一眼的感觉应该是泛型擦除(Type Erasure)和类型推断(Type Inference)导致的. 但当...

    liangzai_cool 评论0 收藏0

发表评论

0条评论

nidaye

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<