Java 泛型擦除的理解及如何获取泛型的实际类型

668 阅读3分钟

泛型

泛型是JDK1.5之后的一项新增特性, 它的本质是参数化类型(Parametersized Type)的应用, 即所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别为泛型类、泛型接口和泛型方法。 Java中的泛型只在程序源代码中存在, 在编译后的字节码文件中就已经替换为原来的原生类型(Raw Type), 并在相应的地方插入强制类型装换代码。 对于运行期的Java语言来说,ArrayList 与 ArrayList 就是同一个类,泛型技术实际上就是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法的实现的泛型称为伪泛型。

java 字节码层面

源代码

public class MyListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("test");
        String list1 = list.get(0);
        System.out.println(list1);
        System.out.println(list);
    }
}

编译命令

javac -encoding UTF-8 MyListTest.java

编译为class文件

public class MyListTest {
    public MyListTest() {
    }

    public static void main(String[] var0) {
        ArrayList var1 = new ArrayList();
        var1.add("test");
        String var2 = (String)var1.get(0); // 这里插入强制类型转换
        System.out.println(var2);
        System.out.println(var1);
    }
}

javap查看字节码

javap -c MyListTest
警告: 二进制文件MyListTest包含com.james.example.MyListTest
Compiled from "MyListTest.java"
public class com.james.example.MyListTest {
  public com.james.example.MyListTest();
    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           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String test
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: aload_1
      18: iconst_0
      19: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;  这里获取的是Object类型
      24: checkcast     #7                  // class java/lang/String 类型转换指令
      27: astore_2
      28: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: aload_2
      32: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      35: getstatic     #8                  /

Java中获取泛型的类型信息

Java泛型的引入,各种场景(虚拟机解析, 反射等)下的方法调用都可能对原有的基础产生影响和新需求, 如在泛型类中如何获取传入的参数化类型等。基于此,JCP组织对虚拟机规范做出了相应的修改,引入了诸如Signature, LocalVariableType Table 等新属性解决伴随泛型而来的参数化类型的识别问题。 Signature 是其中最重要的一项属性,作用是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型不是原生类型,而是包括了参数化类型的信息。

<>: 念做typeof List: E称为类型参数变量 List: Integer称为实际类型参数 List: 整个List称为参数化类型(对应着java.lang.reflect.ParameterizedType接口)

实例化子类中获取泛型

定义一个MyType

public class MyType<T> {
    public Class<T> getTClass() {
        return (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
}

测试

        MyType<String> myType = new MyType<String>(){}; //注意这里是实例化的
        // 在类的外部获取
        Type actualTypeArguments = ((ParameterizedType) myType.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        Type rawType = ((ParameterizedType) myType.getClass().getGenericSuperclass()).getRawType();
        System.out.println(actualTypeArguments);
        System.out.println(rawType);
        System.out.println(myType.getTClass());

测试结果

class java.lang.String
class com.james.example.type.MyType
class java.lang.String

未实例化子类无法获取泛型

测试

MyType<String> myType = new MyType<>(); //注意这里是未实例化的
Type actualTypeArguments = ((ParameterizedType) myType.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
Type rawType = ((ParameterizedType) myType.getClass().getGenericSuperclass()).getRawType();
System.out.println(actualTypeArguments);
System.out.println(rawType);
System.out.println(myType.getTClass());

结果

Exception in thread "main" java.lang.ClassCastException: class java.lang.Class cannot be cast to class java.lang.reflect.ParameterizedType (java.lang.Class and java.lang.reflect.ParameterizedType are in module java.base of loader 'bootstrap')
	at com.james.example.type.MyListTest.main(MyListTest.java:23)

List测试

ArrayList<String> list3 = new ArrayList<String>(){}; 
System.out.println(((ParameterizedType)list3.getClass().getGenericSuperclass()).getRawType());
System.out.println(((ParameterizedType)list3.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
        
ArrayList<String> list6 = new ArrayList<>();// 这里new ArrayList<String>() 结果也是同new ArrayList<>()
System.out.println(((ParameterizedType)list6.getClass().getGenericSuperclass()).getRawType());
System.out.println(((ParameterizedType)list6.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
class java.util.ArrayList
class java.lang.String
class java.util.AbstractList
E

总结

getGenericInterfaces() getGenericSuperclass() import java.lang.reflect.ParameterizedType; getActualTypeArguments() import java.lang.reflect.Type;

Signature属性的出现,Java泛型擦除法所谓的擦除,只是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段获取参数化类型的根本依据。