java 中协变,逆变,不变简单理解
1. 什么是协变、逆变、不变
假设有两个类,Dog和Animal,如果用Dog <= Animal 表示它俩的继承关系。用f(type) 表示类型构造器,一个已知的类型被类型构造器处理后就是一个崭新的类型。
协变就是f(Dog)是f(Animal)的子类,即f(Dog) <= f(Animal);逆变就是f(Animal)是f(Dog)的子类,即f(Aniaml) <= f(Dog);不变就是指f(Dog)与f(Animal)之间没有关系
类型构造器可以是泛型
List<Animal>
, 可以是数组Animal[]
,可以是函数方法method(Animal)
2.java 泛型和数组
- java泛型不支持逆变和协变,只能是不变
List<Animal> animals = new ArrayList<Dog>(); // 编译错误,java泛型不支持逆变和协变,只能是不变
- java数组支持协变,不支持逆变,但也正因为支持协变,数组可能踩坑
Animal[] animals = new Dog[10];
animals[0] = new Dog();
animals[1] = new Cat(); // 运行时异常 java.lang.ArrayStoreException
3.java 泛型协变&上界
- 考虑泛型支持协变后有什么好处,解决了什么问题?
// processAnimals方法中如果不支持泛型协变,那么难道要通过接收类型的不同重写好多的方法吗?太麻烦了!!!
public static void main(String[] args) {
processAnimals(new ArrayList<Cat>()); // 编译错误
proceessAimals(new ArrayList<Dog>()); // 编译错误
}
public static void processAnimals(List<Animal> animals) {
// ...
}
- 在java泛型中加入extends关键字实现支持协变,
<? extends Animal>
其中?代表不确定类型通配符,和extends结合就声明了泛型的上限,表示接收的类型只能是指定类型或是该类型的子类 - 上面说数组支持协变,添加其他类型会出现运行时异常,泛型协变为了杜绝这种隐患,所以泛型协变除了null 可以写,其他的都不能写(编译异常),可以读
- 指定上界的好处,限定类型(编译错误提醒);可以访问上界类型中的方法(要不只能访问Object类中方法)
// 使用extends关键字让泛型支持协变,这样processAniamls方法中的泛型变量就能接收子类集合了
public static void processAnimals(List<> extends Animal> anumals){
}
public static void processAnimalsExtends(List<? extends Animal> animals) {
animals.add(null); // 正常
/**
* 协变不允许传入除null
*/
// animals.add(new Animal()); // 编译错误
// animals.add(new Dog()); // 编译错误
animals.remove(new Dog()); // 不会破坏类型安全
animals.contains(new Dog()); // 不会破坏类型安全
}
public class AKA<T extends Animal>{
public static void main(String[] args) {
AKA<Animal> animalAKA = new AKA<>();
AKA<Cat> catAKA = new AKA<>();
AKA<String> stringAKA = new AKA<String>();// 编译错误
}
}
4.java 泛型逆变&下界
- 使用super关键字声明泛型下界,如
<? super Dog>
,逆变后就可以接收本类型或父类型的泛型类 - 逆变可以添加元素,逆变泛型可以接受本类型及父类型元素,但是添加元素只能添加指定类型或指定类型的子类
public static void testDog(List<? super Dog> dest,list<? extends Dog> src){
for(Dog dog :src){
if(dog.isHappy()){
dest.add(dog);
}
}
}
public static void main(Stringp[] args){
testDog(new ArrayList<Animal>(),new ArrayList<哈士奇>()); // 正确
}
5.应用场景
只读不写:用协变
只写不读:用逆变
又读又写:用不变