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

 

 

 

版权声明:本文版权归作者所有,遵循 CC 4.0 BY-SA 许可协议, 转载请注明原文链接
https://www.cnblogs.com/chen943354/p/15811840.html