一.概念
1.什么是泛型
- 泛型的本质是类型参数化。
- 允许在定义类、接口、方法时使用类型形参,当使用时指定具体类型。
- 所有使用该泛型参数的地方都被统一化,保证类型一致。
- 如果未指定具体类型,默认是 Object 类型。
- 集合体系中的所有类都增加了泛型,泛型也主要用在集合。
泛型是将类型参数化,允许定义在类、接口、方法时使用类型参数,当使用的时候指定具体类型。
泛型主要应用在集合
2.泛型的优点
- 代码需要更精简
- 程序更加健壮
- 编码期,可读性很高
3.类型擦除和桥接方法
泛型是给 javac 编译器使用,在编译器编译之后的 class 文件中是没有泛型信息的,所以泛型的使用不会让程序运行效率收到影响,这个过程称之为擦除。由于类型擦除了,为了维持多态性,需要一些桥接方法类保持多态性,桥接方法是编译器自动生成的
// 最初的代码
publicclassNode<T> {
publicTdata;
publicvoidsetData(Tdata) {
this.data=data;
}
}
publicclassMyNodeextendsNode<Integer> {
publicvoidsetData(Integerdata) {
//....
}
}
// jvm不认识泛型的,把泛型擦除掉, 兼容⽼版本jdk
public class Node {
public Object data;
public void setData(Object data) {
this.data = data;
}
}
public class MyNode extends Node {
// 桥接⽅法,编译器⾃动⽣成
public void setData(Object data) {
setData((Integer)data)
}
public void setData(Integer data) {
//....
}
}
二.泛型上下限
1.泛型的上下限
上限格式:类型名称 <? extend 类> 对象名称说明:只能接收该类型及其子类,指的是参数,不是修改
下限格式:类型名称 <? super 类> 对象名称说明:只能接收该类型及其父类型,指的是参数是父类,添加是该类型自己
2.通配符的上限
/**
* 测试类
* 通配符的上限 List<? extends Type>
* 格式:类型名称<? extends 类> 对象名称
* 意义:只能接收该类型及其子类
*
* @author : kwan
* @version : 1.0.0
* @date : 2022/8/11 16:57
*/
public class Collection_02_extends_super {
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
// 编译器只知道animals里保存的是Animal的子类
//但是并不知道是哪个子类
//Animal 是接口 ,无法添加
List<? extends Animal> animals = dogs;
// animals.add(new Dog()); // 编译不通过
// animals.add(new Animal()); // 编译不通过
//
Animal animal = animals.get(0);
// Dog dog = animals.get(0); // 编译不通过
}
}
3.通配符的下限
/**
* 测试类
* 通配符的下限 List<? supper Type>
* 格式:类型名称<? supper 类> 对象名称
* 意义:只能接收该类型及其父类
*
* @author : kwan
* @version : 1.0.0
* @date : 2022/8/11 16:57
*/
public class Collection_03_extends_super {
public static void main(String[] args) {
List<Dog> as = new ArrayList<>();
as.add(new Dog());
// 编译器只知道bs里保存的是BigDog的父类
List<? super BigDog> bs = as;
// bs.add(new Dog()); // 编译不通过
bs.add(new BigDog());
// Dog a = bs.get(0); // 编译不通过
// BigDog b = bs.get(0); // 编译不通过
}
}
4.上限解读
通配符的上限 List<? extends Type>可以接收该类型及其子类,即可以接收 Type 的所有子类的 List 对象。
使用通配符的上限可以使得我们在定义方法或变量时,可以限制传入的参数类型或赋值的对象类型。例如,我们定义了一个方法,需要传入一个 List 对象,但我们只需要使用 List 中的一部分元素,此时我们可以使用通配符的上限来限制传入的 List 类型,代码如下:
public class Collection_03_extends_super {
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
printList(dogs);
}
public static void printList(List<? extends Animal> list) {
for (Animal n : list) {
System.out.println(n);
}
}
}
在上述代码中,我们使用了通配符的上限 List<? extends Animal>来限制传入的 List 对象类型,可以接收 Animal 及其子类的 List 对象。在方法中,我们只需要遍历 List 中的元素,不需要对 List 进行修改,因此使用通配符的上限可以很好地满足我们的需求。
需要注意的是,通配符的上限只能用于读取操作,不能用于写入操作。即我们可以从 List 中读取元素,但不能向 List 中添加元素。这是因为,通配符的上限限制了传入的 List 对象类型,但不能确定 List 对象的具体类型,因此不能向 List 中添加元素。
5.下限解读
通配符的下限 List<? super Type>可以接收该类型及其父类,即可以接收 Type 的所有父类的 List 对象。
使用通配符的下限可以使得我们在定义方法或变量时,可以限制传入的参数类型或赋值的对象类型。通配符的下限通常用于写入操作,例如,我们定义了一个方法,需要向 List 中添加元素,但我们只能添加类型为 Type 及其父类的元素,此时我们可以使用通配符的下限来限制传入的 List 类型,代码如下:
public class Collection_05_extends_super {
/**
* BigDog继承Dog
*
* @param args
*/
public static void main(String[] args) {
List<Dog> as = new ArrayList<>();
as.add(new Dog());
addToList(as);
}
public static void addToList(List<? super BigDog> list) {
list.add(new BigDog());
list.add(new BigDog());
list.add(new BigDog());
for (Object o : list) {
System.out.println(o);
}
}
}
在上述代码中,我们使用了通配符的下限 List<? super BigDog>来限制传入的 List 对象类型,可以接收 BigDog 及其父类的 List 对象。在方法中,我们向 List 中添加了三个元素,这些元素的类型都是 BigDog 及其子类,因此不会违反通配符的下限的限制。
需要注意的是,通配符的下限只能用于写入操作,不能用于读取操作。即我们可以向 List 中添加元素,但不能从 List 中读取元素。这是因为,通配符的下限限制了传入的 List 对象类型,但不能确定 List 对象的具体类型,因此不能从 List 中读取元素。
三.泛型符号
1.常用符号
本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:
- ?表示不确定的 java 类型
- T (type) 表示具体的一个 java 类型
- K V (key value) 分别代表 java 键值中的 Key Value
- E (element) 代表 Element
2.?和 T 的区别
?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种
// 可以
T t = operate();
// 不可以
?car = operate();
T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。