Java中的泛型是在编译器这个级别实现的,在生成的Java字节码当中是没有泛型中的类型信息的,使用泛型的时候加上的类型参数会在编译的时候去掉,这个过程称作为类型擦除,如在代码中定义的List
泛型的很多奇怪特性都与类型擦除有关:
- 泛型没有自己独有的Class类对象,比如不存在List
.class ,只有List.class; - 静态变量被泛型类所有的实例共享,例如MyClass
中定义静态变量 var , 访问这个静态变量的方式任然是MyClass.var。 - 泛型的参数类型不能用在Java的异常处理中,以为异常处理是由JVM的 Runtime 来处理的,但是由于类型擦除,所以JVM是不知道这个类型信息的存在的,对于JVM来说都是MyException.也就无法执行对应的catche语句。(目前在Intelij IDE中,定义的一个泛型的异常直接报错(Generic class may not extends java.lang.Throwable))
这些原因的根源都是泛型的类型信息只是在编译期存在的,而在程序执行的时候无法找到的。
但是这里又有另外一个问题了,我们在程序运行的时候明明是可以通过API 拿到另一个类的泛型信息的,
class 对象由这两个方法
- getGenericSuperclass()
- getGenericInterface()
那么这两个类拿到的泛型信息又是从哪里获取的呢?
Java文件在编译之后会生成字节码文件,字节码文件的结构如下图所示
接下来,先定义两个类型,
public class MyClass {
List data;
public void setData(List data) {
this.data = data;
}
}
public class MyClassString {
List<String> data;
public void setData(List<String> data) {
this.data = data;
}
}
在编译完成之后,我们可以通过javap 来获取两者生成的字节码
运行 javap -c MyClassString 结果如下
public class com.baidu.accweather.MyClassString {
java.util.List<java.lang.String> data;
public com.baidu.accweather.MyClassString();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void setData(java.util.List<java.lang.String>);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field data:Ljava/util/List;
5: return
}
运行javap -c MyClass 结果如下
public class com.baidu.accweather.MyClass {
java.util.List data;
public com.baidu.accweather.MyClass();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void setData(java.util.List);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field data:Ljava/util/List;
5: return
}
这个时候通过对比发现出列data 的声明不同之外其他的地方完全一样,特别需要注意的是在setData当中,从注释来看 data, 它的类型信息里面已经没有了泛型类型的信息。
接下来我们需要用 -verbose 参数打印堆栈大小、各方法的locals及args参数,以及class文件的编译版本
运行javap -verbose MyClassString , 其常量池的信息输出如下(MyClass 的就不贴出来了)
Classfile /D:/javaworkspace/AccWeather/out/production/AccWeather/com/baidu/accweather/MyClassString.class
Last modified 2017-3-27; size 610 bytes
MD5 checksum caaccea4d689459e3464f2088a3efb34
Compiled from "MyClassString.java"
public class com.baidu.accweather.MyClassString
SourceFile: "MyClassString.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#22 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#23 // com/baidu/accweather/MyClassString.data:Ljava/util/List;
#3 = Class #24 // com/baidu/accweather/MyClassString
#4 = Class #25 // java/lang/Object
#5 = Utf8 data
#6 = Utf8 Ljava/util/List;
#7 = Utf8 Signature
#8 = Utf8 Ljava/util/List<Ljava/lang/String;>;
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/baidu/accweather/MyClassString;
#16 = Utf8 setData
#17 = Utf8 (Ljava/util/List;)V
#18 = Utf8 LocalVariableTypeTable
#19 = Utf8 (Ljava/util/List<Ljava/lang/String;>;)V
#20 = Utf8 SourceFile
#21 = Utf8 MyClassString.java
#22 = NameAndType #9:#10 // "<init>":()V
#23 = NameAndType #5:#6 // data:Ljava/util/List;
#24 = Utf8 com/baidu/accweather/MyClassString
#25 = Utf8 java/lang/Object
{
java.util.List<java.lang.String> data;
flags:
Signature: #8 // Ljava/util/List<Ljava/lang/String;>;
public com.baidu.accweather.MyClassString();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/baidu/accweather/MyClassString;
public void setData(java.util.List<java.lang.String>);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #2 // Field data:Ljava/util/List;
5: return
LineNumberTable:
line 12: 0
line 13: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/baidu/accweather/MyClassString;
0 6 1 data Ljava/util/List;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 6 1 data Ljava/util/List<Ljava/lang/String;>;
Signature: #19 // (Ljava/util/List<Ljava/lang/String;>;)V
}
从setData 方法输出的信息来看, 里面有两张表,一个本地变量表,一个是变量类型表,从 LocalVariableTypeTable 中可以看出来 变量 data 的类型信息是 java/util/List
而最后 Signature 的属性在这里具体指明了是常量池中的#19,这里具体说明了这个方法的信息。