关于泛型是什么以及怎么使用本文不在赘述。在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