纯干货🤔,有点无聊,还没完全搞懂
基本概念
什么是泛型
- 泛型就是将类型进行参数化,定义类或者接口或者方法的时候,指明参数类型是泛型,在使用的时候,需要指定具体的类型。比如创建ArrayList对象的时候,需要指定往集合中插入的元素的类型,Integer或者String或者自定义类型。
泛型的作用
- 使用泛型避免了强制类型转换的情况;而且在编译期就可以检测到类型错误。
- 使用泛型,提高了代码的复用性。比如开发项目中,自定义接口通用的返回结果,使用泛型可以往返回结果类中加入不同类型的返回值。
泛型类、泛型接口、泛型方法
泛型类
基本语法
public class BaseResult<T> {
private String code;
private String message;
private T data;
private Boolean isSuccess;
}
泛型方法
基本语法
<T> 表示声明这个方法是一个泛型方法(带有该标识符的才是泛型方法),T是这个方法的返回值,是已经在类中声明的类型变量,该方法接受一个泛型类Class<T> t;
protected <T> T getTaskData(Object id, String id2, Class<T> t) {
// 操作
return ObjHelper.convertTo(A, t);
}
方法的返回值也可以是其他类型,如void:
protected <T> void getTaskData(Object id, String id2, Class<T> t) {
// 操作
}
泛型方法可以定义在泛型类中,也可以定义在普通类中
public class ArrayAlg {
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
}
这是泛型方法接受两个泛型参数的传入,所以声明的时候使用了<S, T>
public static <S, T> T copy(S source, Sup<T> target) {
T t = target.get();
copy1(source, t);
return t;
}
泛型接口
定义泛型接口
public interface Box<T> {
void add(T item);
T get();
}
// 实现泛型接口
public class IntegerBox implements Box<Integer> {
private Integer item;
public void add(Integer item) {
this.item = item;
}
public Integer get() {
return item;
}
}
// 使用泛型接口
Box<Integer> box = new IntegerBox();
box.add(10);
Integer value = box.get();
实现泛型接口
- 具体类型参数实现:在实现类中显式指定具体的类型参数。这将使实现类只能处理特定类型的数据。
public class IntegerBox implements Box<Integer> {
private Integer item;
public void add(Integer item) {
this.item = item;
}
public Integer get() {
return item;
}
}
在上面的示例中,IntegerBox 类实现了泛型接口 Box<Integer>,并明确指定了类型参数为 Integer。因此,IntegerBox 类只能处理整数类型的数据。
- 保留泛型类型参数:在实现类中继续使用泛型类型参数。这将使实现类具有与泛型接口相同的类型参数,从而保持灵活性。
public class GenericBox<T> implements Box<T> {
private T item;
public void add(T item) {
this.item = item;
}
public T get() {
return item;
}
}
类型变量的限定
什么是类型变量的限定
有些业务场景使用的泛型方法中调用了特定类才有的方法,这时候就需要对类型变量进行约束,限制必须为某个类型;否则会报错(编译出错,还是运行出错?)。
举例子:比较两数的大小,使用了compareTo方法,但只有实现了Comparable接口的类,才能使用这个方法,所以限定T extends Comparable>
public class MinValue{
public <T extends Comparable> T minValue(T number1, T number2) {
if (number1.compareTo(number2) > 0) {
return number2;
}else {
return number1;
}
}
public static void main(String[] args) {
MinValue min = new MinValue();
System.out.println(min.minValue(1, 2));
}
}
在ide中试了下,如果没有将T限制为extends Comparable,ide会直接出现红色(如下图),也就是编译时报错
类型变量限定的语法
通过extends关键字来限定类型变量。有两种限定方式
- 单一限定
限定类型必须为某个类或接口的子类。代码实战:
public <T extends TxBasicResp> T post(String url, JSONObject params, Class<T> clazz) {
T result = restTemplate.postForObject(...);
return result;
}
public abstract class MapperImpl <M extends BaseMapper<T>, T> extends ServiceImpl<M, T> {
}
- 多重限定
指定类型参数必须是多个类或接口的子类,并且只能有一个类(如果有)。
public class MyClass<T extends ClassA & InterfaceB & InterfaceC> {
// 类成员和方法定义
}
一个类型变量有多个限定类型时,使用“&”分隔;而逗号用来分隔多个类型变量。比如:
public class MyClass<T extends ClassA & InterfaceB, S extends Class A> {
// 类成员和方法定义
}
通配符(没写完,还不太懂)
通配符的作用
让泛型程序设计更加灵活。 举个例子:
1.有派生关系的两个类,放到泛型容器之后,无法互相引用(还没找到具体的例子,为什么要互相引用?)
- 定义一个Fruit类及其子类:Apple
class Fruit { };
class Apple { };
- 定义一个未使用通配符的Plate泛型类
class Plate <T> {
private T item;
public Plate(T t) { item = t;}
public void setItem(T t) { item = t;}
public void getItem() { return item;}
}
- 定义一个“水果盘”,逻辑上“水果盘”可以装苹果,但以下语句会报错(类型不兼容):
Plate<Fruit> p = new Plate<Apple> (new Apple());
- 编译器认定的逻辑是这样的:苹果是一种水果,但是装苹果的盘子不是装水果的盘子
- 有派生关系的两个类,放到泛型容器之后,子类所在容器无法使用父类所在容器的方法
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
}
// Employee类
public class Employee {
Integer id;
String name;
public Employee(){
}
public Employee(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId(){
return id;
}
public String getName() {
return name;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
// Manager类
public class Manager extends Employee {
Integer id;
String name;
Double salary;
public Manager(){
}
public Manager(Integer id, String name, Double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public Integer getId(){
return id;
}
public String getName() {
return name;
}
public Double getSalary() {
return salary;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setSalary(Double salary) {
this.salary = salary;
}
}
// PrintBuddiesMain.printBuddies(managerPair)编译会报错
public class PrintBuddiesMain {
public static void printBuddies(Pair<Employee> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
}
public static void main(String[] args) {
Manager manager1 = new Manager(1, "张三", 6000.00);
Manager manager2 = new Manager(2, "李四", 7000.00);
Pair<Manager> managerPair = new Pair<>(manager1, manager2);
Employee employee1 = new Employee(1, "王无");
Employee employee2 = new Employee(2, "周六");
Pair<Employee> employeePair = new Pair<>(employee1, employee2);
PrintBuddiesMain.printBuddies(managerPair);
PrintBuddiesMain.printBuddies(employeePair);
}
}
//以下是不报错的,改写printBuddies方法即可
public class PrintBuddiesMain {
public static void printBuddies(Pair<? extends Employee> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
}
public static void main(String[] args) {
...
}
}
通配符的类别(基础语法)
上界通配符
// 泛型类可以存放 Fruit 及其子类
class Plate <? extends Fruit> {
// 方法体
}
Plate<? extends Fruit> 和 Plate<Apple>最大的区别是:Plate<? extends Fruit>是Plate<Fruit> 以及Plate<Apple>的基类。
下界通配符
class Plate <? super Fruit> {
// 方法体
}
表达的就是相反的概念:一个能放水果以及一切是水果基类的盘子。
对应刚才那个例子,Plate <? super Fruit> 覆盖下图区域
无限制通配符
// 泛型方法接受任意类型的参数
void myGenericMethod(List<?> list) {
// 方法体
}
上下界通配符的副作用
上界通配符
使用上界通配符的类<? extends anyClass> ,不能往对象里存东西,但可以从对象中获取内容。
而且取出来的东西只能放到‘anyClass’类或者其基类中。
比如:
Plate<? extends Fruit> p = new Plate<Apple>(new Apple());
p.set(new Fruit()); // error
p.set(new Apple()); // error
Fruit newFruit1 = p.get();
Object newFruit2 = p.get();
Apple newFruit3 = p.get(); // error
原因:编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。 编译器在看到后面用Plate赋值之后,盘子里没有被标上有“苹果”。而是标识符:capture#1,表示捕获一个Fruit或者其子类。无论是往里插入Fruit还是Apple,都没有办法和capture#1匹配上,所以不允许插入
通配符和类型参数T的区别
根据上述分析,对编译器来说,只要使用类的之后指定了类型,那所有的T都代表同一种类型。但通配符没有这种约束,只知道类里面放了一个东西,具体是什么不清楚。
下界通配符
使用下界通配符Plate<? super Fruit>的泛型类,往对象里存东西可以,但是取出来的时候只能赋值给Object对象。
Plate<? super Fruit> p = new Plate<Fruit>(new Fruit());
p.set(new Fruit());
p.set(new Food());
Fruit newFruit1 = p.get(); // error
Apple newFruit3 = p.get(); // error
Object newFruit2 = p.get();
因为下届通配符,只限定了可以存入类型的最小范围,所以只要存的类是Fruit或者基类就行。取出来的内容,需要使用Object对象来存储,否则可能会报类型不兼容的错误?
PECS(Producer Extends Consumer Super)原则
1、频繁往外读取内容的,适合用上界。
2、经常往里插入的,适合用下界。
可参考文章:
类型擦除和桥方法
什么是类型擦除
Java程序编译期间,将泛型类型转换为非泛型类型。删去类型变量,使用它们的限定变量来替代,如果没有限定变量,则替换为Object类型。如下:
- 没有限定变量的情况
public class Pair {
private Object first;
private Object second;
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirstO { return first; }
public Object getSecondO { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
}
- 有限定变量的情况