Java泛型初级学习笔记
仅为笔记,具体参考廖雪峰Java教程。
泛型中我平常使用最多的就是集合,例如List,Map,Set。
拿ArrayList
举例,在业务中使用时可以传入任何类型。
如:
ArrayList<String> arrayList = new ArrayList<>();
ArrayList<Integer> integerArrayList = new ArrayList<>();
而传入的是什么类型,在你调用时就是什么类型。
String str = arrayList.get(0);
Integer num = integerArrayList.get(0);
这样可以省去大多数不必要的装箱和拆箱,甚至有时不小心还会出现ClassCastException
。
这是泛型最直观的好处,再去看看ArrayList
的源码。
这里的E
的意思是指,可以传入任何类型,当然理所当然的在你调用该类的其他方法时,也会使用到这个泛型。
如最典型的get
方法:
public E get(int index) {
//判断是否下标越界
rangeCheck(index);
//调用内部封装数组的指定下标的所在元素
return elementData(index);
}
在这里就会根据你传入时的数据类型来传出指定类型的数据。
但是在这里需要注意一点,在JVM中,它并不知道你做了这些处理的,这些工作都是编译器帮你做的。
来举个栗子,就比如上面的
ArrayList<Integer> integerArrayList = new ArrayList<>();
实际上在运行中,它是
ArrayList integerArrayList = new ArrayList<>();
这就是常说的泛型擦除。
编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
编译器内部永远把所有类型T
视为Object
处理,但是,在需要转型的时候,编译器会根据T
的类型自动为我们实行安全地强制转型。
这就代表在你拿取数据时所调用的方法,其实都是经过了一个转型的过程。
Integer num = (Integer) integerArrayList.get(0);
只不过这些并没有让你看见。
在这里,廖雪峰老师在博客里指出了它的四个局限。
<T>
不能是基本类型,例如int
,因为实际类型是Object
,Object
类型无法持有基本类型。- 无法取得带泛型的
Class
。 - 无法判断带泛型的类型。
- 不能实例化
T
类型。
可以通过廖雪峰Java教程去看详细解释。
这里要注意泛型的一个严重的问题,也是文章中指出的。
将ArrayList<Integer>
向上转型为ArrayList<Number>
或List<Number>
,虽然Integer
是Number
的子类,但与此同时Float
也是Number
的子类,若是传入一个Float
类型的参数,拆箱时是会出现ClassCastException
的,当然,编译器会直接禁止这种行为。
但是Java在这里提供了另一种方式,也就是extends
通配符,看一下它在代码中的使用。
这是ArrayList
的addAll
方法,可以批量添加的一个方法,只要满足了传入类型是当前类型的子类的条件,就可以传入该参数。
但是在读取时,得以最初的父类泛型为准,因为它并不知道传入的类型是哪个子类,也就是说,可能还会有拆箱的一步。
在写入时使用该通配符显然也是不行的,若一个Class<? extends Number>
传入Class<Double>
显然是可以的,但是Double是不接受setInteger
类型的。
相反,必然也会有可以传入父类型的通配符super
,它的作用正好相反,可以在此传入所传类型的父类。
虽说在传入时可以传入父类型,但在读取时却只有Object
类型能接收,因为它同样也不知道传入的是哪个父类,Object
类型例外,是因为它在这里是所有对象的父类,所以可以用来接收任何值。
因为在读取时,方法的签名会变成
? super T get(int index);
若传入T是Integer
类型,用Number
接收时,编译器无法将Number类型转换为Integer类型。