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

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

你可能感兴趣的:(Java泛型: 类型擦除(type erasure))