Java泛型之类型擦除

作者: 程序熊大 | 来源:发表于2017-12-05 21:56 被阅读497次

本文首发于个人网站:Java阿杜

类型擦除

学过C++模板的,在使用Java泛型的时候,会感觉到有点不疑问,例如:(1)无法定义一个泛型数组、无法调用泛型参数对象中对应的方法(当然,通过extends关键字是可以做到,只是比较麻烦);(2)ArrayList<Integer>和ArrayList<String>在运行时的类型是相同的。Java中的泛型有这些问题,是它的实现机制决定的,即“类型擦除”。

  1. 类型擦除的定义:编译通过后,准备进入JVM运行时,就不再有类型参数的概念,换句话说:每定义一个泛型类型,JVM会自动提供一个对应的原生类;

    public class Holder4<T> {
    
        private T a;
        private T b;
        private T c;
    
        public Holder4(T a, T b, T c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    
        public T getA() {
            return a;
        }
    
        public T getB() {
            return b;
        }
    
        public T getC() {
            return c;
        }
    
        public void setA(T a) {
            this.a = a;
        }
    
        public void setB(T b) {
            this.b = b;
        }
    
        public void setC(T c) {
            this.c = c;
        }
    
    
     public static void main(String[] args) {
         Holder4<Automobile> holder4 = new Holder4<>(new Automobile(),new Automobile(), new Automobile());
    
         Automobile a = holder4.getA(); //编译器帮忙转型,不需要显式转型
         Automobile b = holder4.getB();
         Automobile c = holder4.getC();
     }
    }
    

    在Java中,每定义一个泛型类型,就会自动提供一个对应的原始类型,例如:

    public class Holder4Raw {
    
        private Object a;
        private Object b;
        private Object c;
    
        public Holder4Raw(Object a, Object b, Object c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    
        public Object getA() {
            return a;
        }
    
        public Object getB() {
            return b;
        }
    
        public Object getC() {
            return c;
        }
    
        public void setA(Object a) {
            this.a = a;
        }
    
        public void setB(Object b) {
            this.b = b;
        }
    
        public void setC(Object c) {
            this.c = c;
        }
    
        public static void main(String[] args) {
            Holder4Raw holder4Raw = new Holder4Raw(new Automobile(),new Automobile(), new Automobile());
    
            Automobile a = (Automobile) holder4Raw.getA();  //显示的转型
            Automobile b = (Automobile) holder4Raw.getB();
            Automobile c = (Automobile) holder4Raw.getC();
        }
    }
    
  2. 为什么选择这种实现机制?

    • 在Java诞生10年后,才想实现类似于C++模板的概念,即泛型;
    • Java的类库是Java生态中非常宝贵的财富,必须保证向后兼容(即现有的代码和类文件依旧合法)和迁移兼容(泛化的代码和非泛化的代码可互相调用)基于上面这两个背景和考虑,Java设计者采取了“类型擦除”这种折中的实现方式。
  3. Java泛型依赖编译器实现,只存在于编译期,JVM中没有泛型的概念;那么,编译器做了什么工作呢?(1)set方法是编译期检查;(2)get方法的返回值进行转型,编译器插入了一个checkcast语句。

    我们通过字节码进行观察,可以看出:(1)Holder4和Holder4Raw两个类的字节码完全相同;(2)在main函数的33、41和49行就是编译器插入的checkcast语句;

    public class org.java.learn.generics.Holder4<T> {
      public org.java.learn.generics.Holder4(T, T, T);
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: aload_0
           5: aload_1
           6: putfield      #2                  // Field a:Ljava/lang/Object;
           9: aload_0
          10: aload_2
          11: putfield      #3                  // Field b:Ljava/lang/Object;
          14: aload_0
          15: aload_3
          16: putfield      #4                  // Field c:Ljava/lang/Object;
          19: return
    
      public T getA();
        Code:
           0: aload_0
           1: getfield      #2                  // Field a:Ljava/lang/Object;
           4: areturn
    
      public T getB();
        Code:
           0: aload_0
           1: getfield      #3                  // Field b:Ljava/lang/Object;
           4: areturn
    
      public T getC();
        Code:
           0: aload_0
           1: getfield      #4                  // Field c:Ljava/lang/Object;
           4: areturn
    
      public void setA(T);
        Code:
           0: aload_0
           1: aload_1
           2: putfield      #2                  // Field a:Ljava/lang/Object;
           5: return
    
      public void setB(T);
        Code:
           0: aload_0
           1: aload_1
           2: putfield      #3                  // Field b:Ljava/lang/Object;
           5: return
    
      public void setC(T);
        Code:
           0: aload_0
           1: aload_1
           2: putfield      #4                  // Field c:Ljava/lang/Object;
           5: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #5                  // class org/java/learn/generics/Holder4
           3: dup
           4: new           #6                  // class org/java/learn/generics/Automobile
           7: dup
           8: invokespecial #7                  // Method org/java/learn/generics/Automobile."<init>":()V
          11: new           #6                  // class org/java/learn/generics/Automobile
          14: dup
          15: invokespecial #7                  // Method org/java/learn/generics/Automobile."<init>":()V
          18: new           #6                  // class org/java/learn/generics/Automobile
          21: dup
          22: invokespecial #7                  // Method org/java/learn/generics/Automobile."<init>":()V
          25: invokespecial #8                  // Method "<init>":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
          28: astore_1
          29: aload_1
          30: invokevirtual #9                  // Method getA:()Ljava/lang/Object;
          33: checkcast     #6                  // class org/java/learn/generics/Automobile,get方法的转型
          36: astore_2
          37: aload_1
          38: invokevirtual #10                 // Method getB:()Ljava/lang/Object;
          41: checkcast     #6                  // class org/java/learn/generics/Automobile,get方法的转型
          44: astore_3
          45: aload_1
          46: invokevirtual #11                 // Method getC:()Ljava/lang/Object;
          49: checkcast     #6                  // class org/java/learn/generics/Automobile,get方法的转型
          52: astore        4
          54: return
    }
    

参考资料

  1. 《Java编程思想》
  2. 《Effective Java》
  3. 《Java核心技术》

相关文章

  • 【进阶之路】Java的类型擦除式泛型

    【进阶之路】Java的类型擦除式泛型 Java选择的泛型类型叫做类型擦除式泛型。什么是类型擦除式泛型呢?就是Jav...

  • Android 开发也要掌握的Java知识 - Java泛型

    如果需要看泛型擦除Java泛型擦除 1.Java泛型有什么用?为啥要使用泛型? Java中数组的类型是定义的时候就...

  • Java如何在运行时获取泛型的类型

    Java泛型是伪泛型,会在编译完成时进行类型的擦除,我们无法在运行时获取泛型参数的具体类型(类型擦除会被替换成泛型...

  • Java 泛型之类型擦除和通配符PECS原则

    类型擦除 泛型是Java 5才引入的特性,在这之前,并没有泛型,所以Java的泛型和C++的不一样,是通过类型擦除...

  • JAVA泛型和类型擦除

    什么是类型擦除 Java是使用擦除来实现泛型的。使用泛型后在运行时任何具体的类型信息都被擦除了,关于泛型的处理都是...

  • java泛型

    java的泛型是"伪泛型",为什么这么说。因为泛型只是作用在编译之前,编译之后,泛型都被擦除了(类型擦除)。所以说...

  • java_泛型

    类型擦除正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型基本上都是在编译器...

  • 泛型中 ? super T和 ? extends T的区别

    首先, 说到 Java 的泛型, 我们必须要提到的是Java 泛型的类型擦除机制: Java中的泛型基本上都是在编...

  • Java 泛型与通配符

    参考地址:《Java 泛型,你了解类型擦除吗?》 《Java中的逆变与协变》 《java 泛型中 T、E .....

  • 泛型:类型擦除

    Java 语言引入泛型是为了在编译时提供更严格的类型检查,并支持泛型编程。 为了实现泛型,Java编译器将类型擦除...

网友评论

    本文标题:Java泛型之类型擦除

    本文链接:https://www.haomeiwen.com/subject/tjjhixtx.html