• Java泛型原理:擦除法


    关于泛型是什么以及怎么使用本文不在赘述。在04年发布的jdk5中,Java支持了泛型这个重要的特性。
    Java里的泛型实现方式是擦拭法(Type Erasure),所谓擦拭法是指:虚拟机对泛型其实一无所知,即JVM不认识T,所有的工作都是编译器做的。
    整个过程大概描述就是:Java代码中编写的泛型T,会被编译器重写为Object存储到字节码中。在获取T类型的数据时,又是从Object额外转换类型为T。
     
    为了验证和理解上述过程,我们编写一段演示代码:
    import java.util.ArrayList;
    
    public class MyGenericList<T> {
        private ArrayList<T> _listContainer = new ArrayList<T>();
    
        public void add(T tt) {
            this._listContainer.add(tt);
        }
    
        public T Get(int positionIndex) {
            return _listContainer.get(positionIndex);
        }
    }
    
    public class Main {
    
        public static void main(String[] args) {
            var tt1 = new MyGenericList<String>();
            var tt2 = new MyGenericList<Integer>();
    
            tt1.add("hello1");
            tt2.add(0);
    
            System.out.println(tt1.Get(0));
            System.out.println(tt2.Get(0));
            System.out.println("tt1.getClass() == tt2.getClass() ="+(tt1.getClass() == tt2.getClass()));
    
            if(tt1 instanceof MyGenericList<String>){
                System.out.println("tt1 instanceof MyGenericList<String>  =true");
            }
        }
    }
    

    程序执行结果:

    hello1
    0
    tt1.getClass() == tt2.getClass() =true
    tt1 instanceof MyGenericList<String>  =true
     
    为了进一步验证编译器的结果,使用javacp反编译字节码得到内容如下:
    Compiled from "MyGenericList.java"
    public class MyGenericList<T> {
      public MyGenericList();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: aload_0
           5: new           #7                  // class java/util/ArrayList
           8: dup
           9: invokespecial #9                  // Method java/util/ArrayList."<init>":()V
          12: putfield      #10                 // Field _listContainer:Ljava/util/ArrayList;
          15: return
    
      public void add(T);
        Code:
           0: aload_0
           1: getfield      #10                 // Field _listContainer:Ljava/util/ArrayList;
           4: aload_1
           5: invokevirtual #16                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
           8: pop
           9: return
    
      public T Get(int);
        Code:
           0: aload_0
           1: getfield      #10                 // Field _listContainer:Ljava/util/ArrayList;
           4: iload_1
           5: invokevirtual #20                 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
           8: areturn
    }

      

    Compiled from "Main.java"
    public class Main {
      public Main();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #7                  // class MyGenericList
           3: dup
           4: invokespecial #9                  // Method MyGenericList."<init>":()V
           7: astore_1
           8: new           #7                  // class MyGenericList
          11: dup
          12: invokespecial #9                  // Method MyGenericList."<init>":()V
          15: astore_2
          16: aload_1
          17: ldc           #10                 // String hello1
          19: invokevirtual #12                 // Method MyGenericList.add:(Ljava/lang/Object;)V
          22: aload_2
          23: iconst_0
          24: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
          27: invokevirtual #12                 // Method MyGenericList.add:(Ljava/lang/Object;)V
          30: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;
          33: aload_1
          34: iconst_0
          35: invokevirtual #28                 // Method MyGenericList.Get:(I)Ljava/lang/Object;
          38: checkcast     #32                 // class java/lang/String
          41: invokevirtual #34                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          44: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;
          47: aload_2
          48: iconst_0
          49: invokevirtual #28                 // Method MyGenericList.Get:(I)Ljava/lang/Object;
          52: invokevirtual #40                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          55: getstatic     #22                 // Field java/lang/System.out:Ljava/io/PrintStream;
          58: aload_1
          59: invokevirtual #42                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
          62: aload_2
          63: invokevirtual #42                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
          66: if_acmpne     73
          69: iconst_1
          70: goto          74
          73: iconst_0
          74: invokevirtual #46                 // Method java/io/PrintStream.println:(Z)V
          77: return
    }
    通过的MyGenericList类型的字节码可以看到,两处invokevirtual的参数确实只有Object类型,而没有泛型T相关的内容。可以验证反类型T经过编译后就是Object。
     
    继续分析Main的字节码,看到38行的代码:
    38: checkcast #32 // class java/lang/String
    其中checkcast是jvm的操作码,含义是检查值是否为给定的类型(此处即为String类型),如果不是则抛出异常。
    至此,可以看到Java泛型是编译器层面支持的泛型,而jvm层面并不支持泛型。
     
     
    基于上述分析,Java这种擦除法的泛型会带来几种使用限制:
     
    1、T不能是基本类型int\bool等,比如这种就会报错:类型实参不能为基元类型
    var tt3 = new MyGenericList<boolean>();
    2、泛型T得到的Class都是同一个,即:
    tt1.getClass() == tt2.getClass() =true

  • 相关阅读:
    Windows XP SP1 Privilege Escalation
    A way escape rbash
    A trick in Exploit Dev
    wget.vbs & wget.ps1
    IDEA创建普通java和web项目教程
    初始Mybatis
    JAVA高级面试题
    JVM执行原理
    java-- 位运算
    JAVA---XML
  • 原文地址:https://www.cnblogs.com/chen943354/p/15811840.html
Copyright © 2020-2023  润新知