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泛型之类型擦除

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