java 泛型深刻理解,泛型实现原理——类型擦除

文章介绍了Java中的泛型概念,它的作用在于提升数据类型安全性,通过泛型类、接口和方法实现参数化类型。类型擦除是Java泛型的实现机制,它在编译时擦除泛型信息,保证运行时兼容非泛型代码。由于类型擦除,泛型类型参数在运行时不可知,限制了某些操作,如无法直接创建泛型数组或使用instanceof检测。然而,编译器通过类型检查确保了类型安全。
摘要由CSDN通过智能技术生成

泛型

个人的一点理解,如有错误请指正,谢谢。

1. 什么是泛型?有什么作用?

泛型即参数化类型,用于解决数据类型的安全性问题,通过泛型参数可以指定传入的对象类型。比如创建集合的时候指定了集合的泛型为String类型,就表示该集合中只能存放String类型对象。

泛型注意事项:

  • 泛型不能用基本数据类型
  • 在给泛型指定具体类型后,可以传入该类型或者其子类类型

2. 泛型的使用方式有哪几种?

泛型类,泛型接口,泛型方法。即将泛型定义在类名后面,接口名后面,方法入参出参上。

3. 类型擦除

名词介绍

  • 泛型类型:泛型类型是指具有类型参数的类或接口。List<String>是一个泛型类型

  • 类型参数:在 List<String> 中,类型参数是 String

  • 泛型的通配符:无界通配符 List<?> 表示可以匹配任意类型;上界通配符 List<? extends Number> 表示一个匹配 Number 或其子类类型的列表;下界通配符 List<? super Integer> 表示一个匹配 Integer 或其父类类型的列表。

什么是类型擦除?

Java 泛型的实现原理是通过类型擦除来实现的。类型擦除是指在编译时将泛型类型的信息擦除,将泛型类型转换为它们的原始类型,并将类型参数替换为Object类型或限定类型。

类型擦除的过程主要有以下几个步骤:

  1. 替换类型参数:类型参数会被擦除为 Object 类型或限定类型。例如,List<T>中的T会被擦除为Objectclass MyClass<T extends Number>中的T会被替换为Number
  2. 擦除泛型类型:泛型类型在编译时会被擦除为原始类型。例如,List<String>在编译后会被擦除为List
  3. 类型转换:在泛型代码中,编译器会插入必要的转换操作(类型强制转换、自动装箱/拆箱等)以保持类型安全性。
  4. 类型检查:编译器会在编译时进行类型检查以确保类型安全性。这样可以在编译时捕获类型错误。

泛型通过在编译时进行类型检查,来保证类型安全。编译器会捕获许多类型相关的错误,使得这些错误可以在编译阶段被发现和解决,而不是在运行时导致异常或错误的发生。

一旦程序经过编译并转换为字节码后,泛型的类型信息会被擦除,运行时无法获取泛型类型参数的具体信息。这就意味着在运行时无法直接操作泛型类型参数的具体类型。这也是为什么无法实例化泛型数组、直接获取泛型的具体类型或进行类型判断的原因。

需要注意的是,尽管类型擦除限制了在泛型代码内部操作具体类型参数的能力,但通过一些编程模式和技巧(如通配符、反射、工厂模式等),仍然可以实现泛型的灵活使用和类型安全性。

解释 “由于类型擦除的存在,泛型类型参数的具体类型在运行时是不可知的”,如下:

意味着在泛型代码内部,无法获取泛型类型参数的具体类型信息。举个例子,考虑以下代码:

public class MyClass<T> {
    public void process(T obj) {
        // 无法在运行时获取 T 的具体类型信息
    }
}

MyClass<String> myClass = new MyClass<>();
myClass.process("Hello");

在上述代码中,泛型类 MyClass<T> 中的泛型类型参数 T 在编译时会被擦除为其上界或实际类型。因此,在 process() 方法内部,无法在运行时获取 T 的具体类型信息。无法直接知道 T 是否是 String 类型。

这意味着在泛型代码内部无法使用 T 的具体类型来执行特定的操作,如创建新的 T 对象、调用 T 特定的方法等。因为在运行时,泛型类型参数的具体类型信息已经被擦除。

通过对类型擦除的了解,我们在回过头来看使用泛型的一些限制条件就很清晰明朗了,如下

  1. 泛型类型参数不能是基本数据类型:因为当类型擦除后类型参数会变为 Object 等引用类型,而基本数据类型不能被当做引用类型来处理。

  2. 使用 instanceof 注意点:instanceof 是用来判断一个对象是否为一个类的实例。由于类型擦除的存在,泛型类型参数的具体类型在运行时是不可知的,因此 instanceof 运算符不能直接用于泛型类型参数。

  3. 不能创建一个泛型类型的实例,也不能创建泛型数组:

    T obj = new T(); 	// 不被允许的
    T[] arr = new T[size]; // 不被允许
    

    应为其中的T可能是Object或者其他限界,因此对其 new没有意义。

2和3理论上解释是由于类型擦除,泛型的类型参数的具体类型在运行时是不可知的。

最终案例:

List<String> list = new ArrayList<>();
list.add(str);

在上述代码运行时,通过list.add(str)将元素 str 添加到 List<String> 中时,泛型类型参数的具体信息被擦除了。由于泛型类型参数在运行时不可知,编译器无法确切知道添加的元素的具体类型。

因此,添加的元素 a 会被当作 Object 类型处理,并且存储在 List 内部的对象数组中。从编译器的角度来看,list 是一个 List<String> 类型的对象,但在运行时(JVM角度),List<String> 会被擦除为原始类型 List

当我们从 list 中取出元素时,编译器会自动进行类型转换。例如,通过 String element = list.get(0); 取出索引为 0 的元素时,编译器会将其强制转换为 String 类型,以符合泛型类型参数为 String 的声明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值