泛型的擦除

实际上,从虚拟机的角度看,不存在泛型概念。

        泛型是运用在编译时期的技术:编译时编译器会按照<类型名>的类型对容器中的元素进行检查,检查不匹配,就编译失败。如果全部检查成功,则编译通过,但编译通过后产生的 .class文件中还有<类型名>这个标识,即字节码的类文件中有泛型,但是运行时并没有泛型这就是泛型的擦除。

        可以通过使用javap反编译查看字节码文件,可以看到其中包含泛型

一句话总结就是:在.java文件运用泛型技术时,编译器在文件编译通过后运行时自动擦除泛型标识。

如果需要使用泛型的类型,则需要通过反射机制进行保存

abstract class A1<TF> {
      private Class<TF> clz; // 就是T的具体类型
      // 在构造器中获取T的具体类型
      public A1() {
                ParameterizedType c = (ParameterizedType) this.getClass().getGenericSuperclass(); // 获取到父类型的定义com.yan6.A1<T>
                clz=(Class)(c.getActualTypeArguments()[0]);  //获取真实的类型参数  T

        }
}

由于泛型的擦除,运行时并没有泛型机制,同时也没有使用向下类型转换,那么为何运行时无异常?

这是由于泛型的补偿

List<String> list=new ArrayList<>();

for(String tmp:list) System.out.println(tmp.length());  //泛型的补偿

编译器在擦除泛型后,会自动将类型转换为原定义的泛型,这样就不必再做向下类型转换了。

for(Object tmp:list){
       if(tmp instanceof String){
           System.out.println(((String)tmp).length());
       }
}

泛型的擦除和补偿这两个机制都是编译器内部自动完成的。

可以通过反射获取类型参数

泛型的局限性

        - 不能使用基本类型
        - 不能使用泛型类异常
        - 不能使用泛型数组

不能实例化参数类型对象。例如T ob = new T();

import java.lang.reflect.ParameterizedType;

public class Test1 {
	public static void main(String[] args) throws Exception {
		B1 bb=new B1();
		System.out.println("dd:"+bb.getTt());
	}
}

abstract class A1<TF>{
	private Class<TF> clz;  //就是T的具体类型
	private TF tt;
	
	//在构造器中获取T的具体类型
	public A1() throws Exception {
		ParameterizedType c=(ParameterizedType)this.getClass().getGenericSuperclass();  //获取到父类型的定义  com.yan6.A1<T>
		clz=(Class)(c.getActualTypeArguments()[0]);   //获取真实的类型参数  T
		tt=clz.newInstance();
		System.out.println(c.getActualTypeArguments()[0]);  //class java.lang.String
//		Class c=this.getClass(); //class com.yan6.B1
//		System.out.println(c);
	}
	public Class getParent() {
		return this.clz;
	}
	public TF getTt() {
		return tt;
	}
	public void setTt(TF tt) {
		this.tt = tt;
	}
}

class B1 extends A1<String>{
	public B1() throws Exception {
		super();
	}
}
import java.lang.reflect.Method;

public class Test2 {
	public static void main(String[] args) throws Exception {
		String clz="java.util.Date";
		System.out.println(Class.forName(clz).newInstance());
		
		A2 aa=new A2();
//		aa.pp();
		
		Method method=aa.getClass().getDeclaredMethod("pp");
		method.setAccessible(true);
		Object res=method.invoke(aa);
		System.out.println(res);
	}
}

class A2{
	private String pp() {
		return "neimengren";
	}
}
<think>好的,我现在要回答用户关于Java擦除的问题。首先,我需要整理一下已有的引用资料,确保理解正确。用户提供了五个引用,内容涉及擦除的原理、实现方式、擦除规则、影响以及相关的限制和解决方案。 首先,擦除的原理是什么?根据引用2和引用3,Java在编译时会被擦除,替换为原生类,并在需要的地方插入强制转换。这意味着在运行时,信息不存在,比如ArrayList<String>和ArrayList<Integer>在运行时都是同一个类。 接下来,擦除的规则。引用1和引用4提到有无界擦除、上界擦除和下界擦除。例如,无界的参数会被替换为Object,有上界的替换为上界类,有下界的可能替换为Object。需要确认这些规则的具体情况,以及如何验证这些规则,比如通过反射或者查看字节码。 然后是影响部分。用户可能关心擦除带来的问题,比如类信息在运行时不可用,导致无法直接实例化,或者无法进行类检查。引用4中提到了两个经典问题:在类中获取实际类和在接口回调中获取类,可能需要通过反射技巧来解决,比如获取父类的参数化类。 另外,引用5提到原始类不支持,比如不能使用List<int>,而必须用包装类,这会带来自动装箱的开销。同时,项目Valhalla可能在未来解决这个问题,但现在还是限制。 现在需要将这些信息组织成结构清晰的回答,分点解释原理、影响,并举例说明。同时,确保使用正确的中文表达和LaTeX格式,比如代码块用反引号,数学公式用$...$,独立公式用$$...$$。不过根据用户提供的示例,似乎数学公式的例子并不直接相关,可能在这个问题中不需要使用数学公式,但需要遵循格式要求。不过用户的问题是关于Java,可能涉及代码示例,所以需要正确使用代码块的格式。 最后生成相关问题,可能包括擦除的优缺点,如何绕过擦除限制,桥接方法的作用,类擦除与类安全的冲突,以及原始类不支持的原因等。 需要检查引用是否正确标注,比如引用[^1]到[^5]是否正确对应提供的资料。根据用户提供的引用,引用1到5分别对应不同的内容,需要确保在回答中正确引用,比如提到桥接方法时引用,提到擦除原理时引用[^2][^3],原始类限制引用[^5]等。 现在组织回答的结构: 1. 擦除的原理:编译时擦除,替换为原生类,插入强制转换。 2. 擦除规则:无界、上界、下界擦除的具体情况。 3. 主要影响: - 运行时类信息丢失 - 类检查限制 - 桥接方法的作用 - 原始类不支持 4. 解决方案和技巧:反射获取信息,桥接方法等。 5. 示例代码:比如类擦除后的代码示例。 6. 相关问题。 在回答过程中,需要确保每个引用的位置正确,比如提到桥接方法时引用,提到擦除实现引用[^2],原始类问题引用。 另外,用户可能对类擦除带来的类安全问题有疑问,比如为什么Java选择这种方式,可能与兼容性有关,保持与旧版本字节码的兼容性,引用2提到这是为了作为语法糖实现,确保兼容性。 可能还需要提到类擦除导致的一些常见错误,比如无法创建数组,或者instanceof无法使用,这些可以结合引用中的例子说明。 检查是否有遗漏的重要点,比如桥接方法在保持多态性的作用,引用1中的演示案例可能需要解释,但在回答中可能需要简化。 最后,确保回答符合用户的要求:结构清晰,分步骤解释,真实可靠,引用正确,使用中文,代码块正确,生成相关问题。</think>### Java擦除原理及影响解析 #### 一、擦除原理 Java通过**类擦除(Type Erasure)**实现,其核心规则为: 1. **编译期擦除**:信息在编译后被替换为原始类(如`Object`或指定上界类),并插入强制类转换代码。 - 无界类(如`<T>`)擦除为`Object`,例如`List<T>`变为`List`。 - 有上界的类(如`<T extends Number>`)擦除为上界类`Number`。 - 有下界的类(如`<? super Integer>`)擦除为`Object`。 2. **运行时无信息**:类在运行时会丢失类参数信息,例如`List<String>`与`List<Integer>`在JVM中均为`List`。 ```java // 编译前 List<String> list = new ArrayList<>(); // 编译后(通过反编译查看) List list = new ArrayList(); // 类参数被擦除 String s = (String) list.get(0); // 插入强制转换 ``` #### 二、主要影响 1. **运行时类限制** - **无法通过反射获取**:例如`new ArrayList<String>().getClass()`无法得知`String`类参数[^4]。 - **无法直接实例化对象**:`new T()`会编译报错,因为实际类未知[^3]。 2. **类安全检查局限性** - 转换错误可能在运行时才暴露。例如将`List<Integer>`强制转换为`List<String>`时,编译期无法检测。 3. **桥接方法(Bridge Method)** - 为实现多态性,编译器会生成桥接方法。例如类继承时,子类需通过桥接方法覆盖父类方法[^1]。 4. **原始类不支持** - 如`List<int>`不合法,需使用包装类`Integer`,导致自动装箱/拆箱的性能开销。 #### 三、解决方案示例 1. **获取实际类** 通过反射获取父类或接口的参数: ```java public class MyList<T> { public Class<T> getType() { return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; } } ``` 2. **绕过实例化限制** 使用工厂方法传递`Class<T>`参数: ```java public static <T> T createInstance(Class<T> clazz) throws Exception { return clazz.getDeclaredConstructor().newInstance(); } ``` ####
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值