确认泛型擦除方法
简单来说,泛型擦除就是编译器在编译的时候,把泛型代码转换为普通的Java字节码,即字节码中没有泛型。
如何证明编译器进行了泛型擦除呢?
- 既然是进行了擦除,那么泛型的类型应该是Object或者是泛型类型的上界。
- 直接查看字节码文件
准备泛型类
准备一个泛型类,有两个私有变量,并通过构造函数初始化。
class GenericErasureClass<T, K extends String> { private T t; private K k; public GenericErasureClass(T t, K k){ this.t = t; this.k = k; } }
方法一,在运行时获取泛型的类型
在客户端代码中创建泛型类的实例对象,然后通过反射的方式查看对象的私有变量的类型。
public class GenericErasureExample { public static void main(String[] args) { GenericErasureClass<String, String> genericErasureClass = new GenericErasureClass<>("t", "k"); Field[] declaredFields = genericErasureClass.getClass().getDeclaredFields(); for (Field field : declaredFields){ System.out.println(field.getGenericType() + ":" + field.getName() + ":" +field.getType()); } } }
输出结果如下:
T:t:class java.lang.Object K:k:class java.lang.String
从输出结果看泛型类型T,被擦除了,变成了Object类型;泛型类型K,由于设置泛型上界类型String,所以也是被擦除了。
方法二,通过字节码文件查看
以下使用javap命令查看字节码
javap -c -p -s GenericErasureClass.class
Compiled from "GenericErasureExample.java" class com.foo.GenericErasureClass<T, K extends java.lang.String> { private T t; descriptor: Ljava/lang/Object; private K k; descriptor: Ljava/lang/String; public com.foo.GenericErasureClass(T, K); descriptor: (Ljava/lang/Object;Ljava/lang/String;)V Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: aload_1 6: putfield #2 // Field t:Ljava/lang/Object; 9: aload_0 10: aload_2 11: putfield #3 // Field k:Ljava/lang/String; 14: return }
泛型参数被擦除,字段t 的描述符是 Ljava/lang/Object,表示在字节码层面,它的类型被看作是 Object,这是泛型T 的默认擦除类型。字段k 的描述符是 Ljava/lang/String,它的类型被明确为 String,这与泛型类型定义 K extends java.lang.String 的上界是一致的。
公共构造函数的描述符也是同理,Ljava/lang/Object;Ljava/lang/String;
从以上的分析中,可以明显的看到Java泛型的擦除效果。
但是,私有变量t、k的类型还有泛型参数T、K是怎么回事呢?请参考:泛型擦除后仍然保留的泛型信息
总结
尽管在源代码级别我们使用了泛型,但在编译后的字节码中,这些泛型信息已经被擦除,并被替换为它们的界限或默认类型。