泛型
个人的一点理解,如有错误请指正,谢谢。
1. 什么是泛型?有什么作用?
泛型即参数化类型,用于解决数据类型的安全性问题,通过泛型参数可以指定传入的对象类型。比如创建集合的时候指定了集合的泛型为String类型,就表示该集合中只能存放String类型对象。
泛型注意事项:
- 泛型不能用基本数据类型
- 在给泛型指定具体类型后,可以传入该类型或者其子类类型
2. 泛型的使用方式有哪几种?
泛型类,泛型接口,泛型方法。即将泛型定义在类名后面,接口名后面,方法入参出参上。
3. 类型擦除
名词介绍
-
泛型类型:泛型类型是指具有类型参数的类或接口。
List<String>
是一个泛型类型 -
类型参数:在
List<String>
中,类型参数是String
。 -
泛型的通配符:无界通配符
List<?>
表示可以匹配任意类型;上界通配符List<? extends Number>
表示一个匹配Number
或其子类类型的列表;下界通配符List<? super Integer>
表示一个匹配Integer
或其父类类型的列表。
什么是类型擦除?
Java 泛型的实现原理是通过类型擦除来实现的。类型擦除是指在编译时将泛型类型的信息擦除,将泛型类型转换为它们的原始类型,并将类型参数替换为Object
类型或限定类型。
类型擦除的过程主要有以下几个步骤:
- 替换类型参数:类型参数会被擦除为
Object
类型或限定类型。例如,List<T>
中的T
会被擦除为Object
,class MyClass<T extends Number>
中的T
会被替换为Number
。 - 擦除泛型类型:泛型类型在编译时会被擦除为原始类型。例如,
List<String>
在编译后会被擦除为List
。 - 类型转换:在泛型代码中,编译器会插入必要的转换操作(类型强制转换、自动装箱/拆箱等)以保持类型安全性。
- 类型检查:编译器会在编译时进行类型检查以确保类型安全性。这样可以在编译时捕获类型错误。
泛型通过在编译时进行类型检查,来保证类型安全。编译器会捕获许多类型相关的错误,使得这些错误可以在编译阶段被发现和解决,而不是在运行时导致异常或错误的发生。
一旦程序经过编译并转换为字节码后,泛型的类型信息会被擦除,运行时无法获取泛型类型参数的具体信息。这就意味着在运行时无法直接操作泛型类型参数的具体类型。这也是为什么无法实例化泛型数组、直接获取泛型的具体类型或进行类型判断的原因。
需要注意的是,尽管类型擦除限制了在泛型代码内部操作具体类型参数的能力,但通过一些编程模式和技巧(如通配符、反射、工厂模式等),仍然可以实现泛型的灵活使用和类型安全性。
解释 “由于类型擦除的存在,泛型类型参数的具体类型在运行时是不可知的”,如下:
意味着在泛型代码内部,无法获取泛型类型参数的具体类型信息。举个例子,考虑以下代码:
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
特定的方法等。因为在运行时,泛型类型参数的具体类型信息已经被擦除。
通过对类型擦除的了解,我们在回过头来看使用泛型的一些限制条件就很清晰明朗了,如下
-
泛型类型参数不能是基本数据类型:因为当类型擦除后类型参数会变为
Object
等引用类型,而基本数据类型不能被当做引用类型来处理。 -
使用
instanceof
注意点:instanceof
是用来判断一个对象是否为一个类的实例。由于类型擦除的存在,泛型类型参数的具体类型在运行时是不可知的,因此instanceof
运算符不能直接用于泛型类型参数。 -
不能创建一个泛型类型的实例,也不能创建泛型数组:
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
的声明。