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

本文深入探讨Java泛型的类型擦除原理,通过字节码层面的分析揭示泛型在运行时的表现。同时,通过实例展示了如何在子类中获取泛型的类型信息,以及在实例化和未实例化子类中的差异。最后,讨论了通过反射获取泛型信息的方法,并提供相关测试案例。
摘要由CSDN通过智能技术生成

泛型

泛型是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属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段获取参数化类型的根本依据。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值