泛型与集合
泛型
本篇博客主要参考码出高效-Java开发手册
泛型的基本概念
泛型的定义和本质
- 泛型指的是可以将类型作为参数进行传递,其本质上就是类型参数化,泛型只会存在于编译期,在运行期会进行参数擦除。关于泛型的擦除,如果泛型没有设置类型上限,则泛型会转化为Object类型,如果设置上限则会转化为设置的上限。最后再进行类型的强转,转化为我们需要的类型。
- 除了基本类型之外,其他的都可以都可以作为泛型。泛型可以定义在类、接口、方法中,编译器通过尖括号和尖括号中的字母来解析泛型,有些字母是约定俗成的:
- E代表Element,表示集合中的元素。
- T代表the Type of object,表示某个类
- K代表key,V代表value,用于键值对元素
下面代码选自码出高效:
public class GeniecTestDemo<T> { /** * @param type0 * @param type1 * @param <String> * @param <T> * @param <Alibaba> * @return */ public static <String, T, Alibaba> String getString(String type0, Alibaba type1) { return type0; } public static void main(String[] args) { Integer integer = Integer.valueOf("123"); Long aLong = Long.valueOf("232324"); //返回的值也是第一个传入的类型的泛型 Integer string = getString(integer, aLong); } }
针对本段代码的说明:
- 此处的String不是java.lang.String中的String,而是自己定义的泛型,和泛型T、Alibaba效果一样
- 类名后的泛型T和方法中的泛型T互相不影响,相互独立
- 尖括号的位置在类名之后或者在方法返回值之前
- 泛型在定义处只有执行Object方法的能力,即在get方法内部无法使用type0.intValue和type1.longValue方法完成
泛型的优势:
- 避免类型的强制转换
- 在编译期内进行更强的类型检查
- 增强代码的复用
泛型的使用
泛型定义位置
-
泛型定义的位置:
- 泛型定义在方法上:在调用方法时,可以使用任何类型的参数;泛型定义必须放在返回值前,修饰词后。
- 泛型定义在类上,创建该类对象时,明确这个对象使用的参数类型。
-
静态方法能否使用泛型
- 静态方法不能使用类上定义的泛型,因为泛型是需要通过对象来明确的,静态方法不需要对象,所以无法用该泛型。
- 如果静态方法要使用泛型,只能将泛型定义在方法上。
泛型的使用细节
<T>、<?>、Object与List、List<?>、List<Object>
解决这个问题首先弄清楚这三位是什么,以及在JDK源码中的应用,第一个是泛型类型参数T,第二个是泛型通配符,第三个是Object类型.
类型参数和Object类的区别
其实在泛型没推出来之前,在集合容器中使用Object类很频繁,泛型推出来之后给我们带来的是使用的便利,具体的优势如下:
- 类型安全:放入什么、取出什么,不用担心抛出ClassCastException
- 提升可读性:编码阶段显示知道泛型集合、泛型方法等处理对象的类型是什么
- 提高代码的重用性
看起来有点眼熟是不是?这不就是为什么要是用泛型吗?是的。
泛型通配符
泛型通配符含义
泛型通配符含义:?代表的是未知类型,所以涉及到?的操作,一定与具体类型无关。
泛型通配符的形式
- <?> 无限通配符 一般搭配容器使用,只有读的能力,没有写的能力
- <? extends T> 定义了泛型上限,限定了泛型的类型只能是T或者是T的子类,只有读的能力,没有写的能力
- <? super T> 定义了泛型的下限,泛型的参数只能是T或者是T的父类,有读取的能力和部分写的能力
泛型通配符?和泛型参数T
关于这个问题,这个老师讲的比较透彻,本文也是吸取了部分他的内容。
https://blog.csdn.net/sinat_32023305/article/details/83215751
选什么,老师把好处都说了
- 首先是两者可以互换的地方
- List<?> mlist、List<T> mlistT
- List<? extends A> mlist、List<T extends A> mlistT
在这两个地方中通配符?和泛型参数T是一样的
- 两者的区别
- List<?>表示不确定的类型,代表范围内任意类型,List<T>表示确定的类型(一旦在创建对象或者方法调用时该类型即是已经确定),代表范围内所有类型
[图片上传失败...(image-c86b0e-1597131632906)]
从使用者角度出发,通配符代表的泛型是不确定的,即使在方法中调用或者在初始化对象时将该泛型确定之后,对于该泛型也只能做读取、判空等操作,但是不能增加元素;
这边需要对读取做两个说明,
- 当返回值需要和泛型相关时,就不能使用。
- 不能对数据进行增加操作增加的操作,
只有泛型通配符?才支持super关键字,泛型T不支持super关键字,无法对泛型的下限进行限定
通配符中数据不能增加,原因在前文已经说明了。泛型参数T可读取、可修改。泛型参数T在类型实例化的时候,泛型的类型就已经确定。
- 两者的使用
- 通配符的优势在于代码简洁,可读性好,因而对于数据我们如果只需要读取,并且返回值对该泛型没有任何依赖时,选择使用?,否则使用泛型参数T。
在JDK源码中,这部分代码在集合类Collections中表现的很明显
public static int indexOfSubList(List<?> source, List<?> target) {
.....
}
该方法作用是查找集合的某一子集在该集合中的位置坐标,对数据不需要进行修改、增加的操作,因而选择通配符没有选择泛型参数
- 在日常的使用过程中,两者往往是配合使用,比如日常使用频率比较高的Collections的排序
public static <T> void sort(List<T> list, Comparator<? super T> c)
List<?> List List<Object>
这个问题出自码出高效,书中也是做了解答。
对于非泛型集合List,可以赋值给任意泛型集合,所以图中的几个集合的定义没有问题。
-
首先是List和List<Object>
看起来没有区别不是吗?
两者都可以添加任意元素(Java中Object类是所有类的父类),但是在赋值时就会出现问题了。
请看下面的代码
这段代码有两个问题
public static void main(String[] args) {
//List和List<Object>的区别
List<Object> objects = new ArrayList<>();
List mLists = new ArrayList();
List<Long> longLists=objects;
List<Long> longLists1=mLists;
//数组和集合的协变性,
ArrayList<Integer> integers = new ArrayList<>();
integers.add(1234);
List<Object> Objects1=integers;
Integer[] integers1 = {1};
Object [] mObjects1=integers1;
}
这段代码有两个问题:
- 将Object类型的集合赋值给Long型的很显然是不可以的,因为List<Object>中对集合的泛型类型作了限制,只能接受Object类型的参数,将Object类型的集合赋值给Integer类型的集合显然是不符合规范
- 按照数组可以协变,那么集合的泛型是否可以协变呢,关于协变,可以参照这个大佬的博客
答案是不可以。
数组的协变性(covariant)是指如果类Base是类Sub的基类,那么Base[]就是Sub[]的基类。而泛型是不可变的,List<Base>不会是List<Sub>的基类,更不会是它的子类。
对照代码部分就是integer类型的集合是不可以赋值给Object类型的,在编译阶段就会报错。
- 关于List<?>则相对简单,List<?> 不支持添加任何元素,可以remove和clear元素,List<?> 作为参数接受外部的集合,或者返回不知道具体类型的集合
List<? extends T> 和 List<? super T>区别
List<T>只能放置一种类型,为放置解决多种受泛型约束的类型,出现了List<?extends T>和List<? super T>这两种
- 首先我们看一下结论,List<? extends T>的特点如下:
- 可赋值给T及T的子类集合
- 除了null之外,任何元素不得添加进<? extends T>中
综合以上特点,我们可知List<? extends T>适合消费元素为主的场景,取出元素反而能加上限
- List<? super T>的特点:
- 可赋值给T及T的父类
- 可添加T及T的父类,下界为T
-
取出功能受限,只能取出Object类型的对象
综合以上特点,List<? super T>适合生产元素为主的场景,取出元素会发生类型擦除。
下面看示例代码:
这段代码可以看到有明显的编译错误:
- 赋值的问题
List<? extends Cat> 可接受Cat及Cat子类集合的赋值
List<? super Cat> 可接受Cat及Cat父类集合的赋值(object类的集合也可以),
在赋值的两个操作中有编译报错源于此 - 放入新元素
List<? extends Cat> 任何类型对象均不可放入,extendsCatList的三行报错缘于此
List<? super Cat> 只可添加Cat及Cat的子类。 - 取出元素
List<? extends Cat>取出元素自动向上转型为Cat
List<? super Cat>取出元素会发生类型丢失,只能返回object对象