好的软件的作用是让复杂的东西看起来简单。
java中协变跟逆变是对泛型类的继承关系的表述.
如:
List<Number>
和List<Integer>
之间是没有继承关系的.
但是直观上会觉得, Integer
是Number
的子类, 所以List<Integer>
应是List<Number>
的子类.
如果想要这种效果, 就要用协变.
List<? extends Number>
这样 List<Integer>
就能成为List<? extends Number> 子类, 也就是可以赋值
List<Integer>b = new ArrayList<>();
List<? extends Number> a = b;
这里如果你想要相反的效果, 则用逆变,List<? super Number>
这样继承关系就会相反.
那么什么时候用协变,逆变?
协变主要是用在函数的返回值上,逆变用在函数参数上,这样的规则也就能遵循里氏替换原则
.
如Function
, 在这里R
作为函数的返回值, 所以这个泛型要协变, 而T
用在函数的参数上所以要用逆变
Function<? super Dog,? extends Animal> f1;
这里举个例子
假设有以下继承关系:
车 > 轿车 > 标准轿车 > 高级轿车
现在有一个人声称自己能修理所有的标准轿车, 所以发出了以下公告:
修理(List<标准轿车> cars)
假设我现在有List<轿车>
和 List<高级轿车>
那么这个人到底能修理哪个呢? 从上面的函数声明来看都不可以.
再来看看这个人的声明
他说能够修理所有标准轿车
那么因为标准轿车扩展了轿车, 所以如果能够修理标准轿车, 那么应当可以修理轿车
所以这个函数应当可以接受所有标准轿车
的父类
也就是说 List<轿车>
能够传入 以List<标准轿车>
为参数的函数
换句话说 List<轿车>
是List<标准轿车>
的子类, 这样才能传入参数
所以上面的公告要用逆变, 改成如下:
修理(List<? super 标准轿车> cars)
也许也不会有人想把自己的高级轿车交给这家伙.
以此类推, 函数的返回值应当用协变, 这样既能满足里氏替换原则
了