为什么要使用协变和逆变?

上一节讨论了什么是协变和逆变,这一节讨论一下为什么会有协变和逆变。

java中有一个特性叫做继承,一个类叫做动物,动物的种类繁多,不能将所有的动物属性和行为都放在一个类中,于是有了具体的动物,比如猫,狗,鸡鸭鹅等等,这些都是动物,现在有个方法叫做治疗,治疗可以治疗所有的动物,但是你写治疗(动物),治疗(猫),治疗(狗)… …这个就太费劲了,如果将猫,狗等都作为动物的子类,那只需要治疗(动物)就可以了,当然这个只是继承的其中一种应用。

java中还有一个特性叫泛型,简单说就是类型的类型,比如java中的List这中的类型就是泛型,可以在编写代码的时候指定List中的具体类型,这样,当我在写一个函数或者容器的时候,就可以更加的方便,比如有一个类型叫做笼子,这个笼子可以装鸟,可以装猫,具体装什么可以通过泛型来确定,笼子[Cat],笼子[Bird],这就比笼子Cat和笼子Bird写两个除了类名不同,里面操作完全一样的类要高明很多。

说了这么多,如何让上述两个特性进行结合呢?就要用到协变和逆变了,按照正常的java来说如果Cat是Animal的子类,但是List和List确是两个完全没有关系的类,如何证明这一点呢?看下面代码:

import java.util.ArrayList;import java.util.List;public class Test1 {public static void main(String[] args) {Animal animal = new Animal();Cat cat = new Cat();List animals = new ArrayList();List cats = new ArrayList();heal(animal);heal(cat);heal(animals);//heal(cats);报错Cannot resolve method 'heal(java.util.List)'//heal2(animals);heal2(cats);}public static void heal(Animal animal) {}public static void heal(List animalList){}public static void heal2(List cats){}}class Animal{}class Cat extends Animal{}

上述代码定义两个类,一个Animal,一个Cat,Cat是Animal的子类。声明了三个方法,一个是接收Animal,一个是接收List,一个接收List。

从代码来看,接收Animal的方法,不仅可以接收Animal,还可以接收Cat,这个就是里式替换原则,子类可以代替父类。

然后接收List的方法只能接收animals,而接收List的方法只能接收cats,说明List和List这两个类型完全没有任何关系。

那么如何让接收List的方法也接收List呢?就需要用到协变,将接受List的方法改成如下形式:

public static void heal(List animalList){}

这样做,在保证原有的接收List的同时,还可以接收List类型的参数,因为这个?extends Animal就让这个入参编程了协变,只要是Animal子类的List,就都可以传入到这个方法当中。

从上面的例子可以看出,由于协变的存在,让原本没有什么关系的集合也存在了继承关系。

具体的用法,下一节继续讨论。