【JVM】泛型的类型擦除

文章目录

前言

开始之前,先上菜:

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
		
System.out.println(l1.getClass() == l2.getClass()); //true

泛型知识归纳1. 2.

WHY

1.字节码的生成与加载(虚拟机外部完成)
编译器先将java类文件转换为字节码.Class文件(含 魔术与Class文件版本号、常量池、访问标志、类索引、父类索引与接口索引集合、字段表集合、方法表集合、属性表集合 等)。满足一定条件(譬如new实例化对象、对类的静态字段、静态方法操作、对类进行反射调用等)触发类的加载与初始化,根据类的全限定名利用特定类加载器来获取定义此类的二进制字节流,并将字节流的静态存储结构转化为方法区的运行时数据结构,并在内存中生成这个类的Class对象,作为方法区该类的数据的访问入口
PS:字节码装载完事后,就是执行引擎的事了(通常包含解释器与即时编译器JIT)
2.语法糖
不影响程序功能,只为增加程序的可读性,(譬如 Java中的泛型、变长参数、自动装箱/拆箱等)
解语法糖:虚拟机运行时不支持这些语法,在编译阶段还原回简单的基础语法结构
PS:java是静态类型语言(虽然其支持动态扩展),其在编译期字节码字节码时类型就被唯一确定了。

在语法糖时为啥非得强调是Java的泛型呢,因为其是
伪泛型:List和 List只在程序源码中存在,在编译后的字节码文件中,已经替换为原来的原生类型,并且在相应的地方插入了强制转换代码。实现方式:类型擦除 List和 List在 JVM 中的 Class 都是 List.class
而真泛型:List和 List在系统运行期生成,有自己的虚方法表和类型数据。 实现方式:类型膨胀

3.JVM中的泛型类型

public class Erasure <T extends String>{
    
    
//	public class Erasure <T>{
    
    
	T object;
	public Erasure(T object) {
    
    
		this.object = object;
	}
	public void add(T object){
    
    
	}
}
Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();

Field[] fs = eclz.getDeclaredFields(); //查看JVM中的泛型成员变量的类型
for ( Field f:fs) {
    
    
	System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
	//Field name object type:java.lang.String
}
//在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T>则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限。

Method[] methods = eclz.getDeclaredMethods();
for ( Method m:methods ){
    
    
	System.out.println(" method:"+m.toString());
	//method:public void com.frank.test.Erasure.add(java.lang.Object)
}
//也就是说,如果你要在反射中找到 add 对应的 Method,你应该调用 getDeclaredMethod("add",Object.class)否则程序会报错,提示没有这么一个方法,原因就是类型擦除的时候,T 被替换成 Object 类型了。

3.类型擦除的利用
类型擦除,是泛型能够与之前的 java 版本代码兼容共存的原因。但同时它也会抹掉很多继承相关的特性,这是它带来的局限性。
当泛型类型被指定为具体类型时,传入参数的类型就会被限制,基于对类型擦除理解,利用反射,我们可以绕过这个限制。

public class ToolTest {
    
    
	public static void main(String[] args) {
    
    
		List<Integer> ls = new ArrayList<>();
		ls.add(23);
//		ls.add("text");
		try {
    
    
			Method method = ls.getClass().getDeclaredMethod("add",Object.class);
			method.invoke(ls,"test");
			method.invoke(ls,42.9f);
		} catch (Exception e) {
    
    
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
}

补充:反射的MVC应用场景
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_40265247/article/details/108228183