参考:(https://www.cnblogs.com/en-heng/p/5041124.html https://www.jianshu.com/p/2bf15c5265c5 )
里氏替换原则(LSP)
在介绍协变和逆变之前,先引入里氏替换原则.
所有引用基类(父类)的地方必须能透明地使用其子类对象.
- 子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法
- 子类中可以增加自己的方法.
- 当子类覆盖或实现父类的方法时,方法的形参要比父类方法的更为宽松
- 当子类覆盖或实现父类的方法时,方法的返回值要比父类更严格.
根据LSP,我们在实例化对象的时候,可以用其子类进行实例化,比如:
Number num = new Integer(1);
定义
逆变与协变是用来描述类型转换后的继承关系,其定义:如果A,B表示类型,f(.)表示类型转换,≤表示继承(比如:A≤B表示A是由B派生出来的子类)
- f(.)是逆变的,当A≤B时有f(B) ≤f(A) 成立;
- f(.)是协变的,当A≤B时有f(A)≤f(B)成立;
- f(.)是不变的,当A≤B时上述两个式子都不成立,即f(A)与f(B)相互之间没有继承关系
类型转换
接下来,看看java中常见的类型转换的协变性,逆变性或不变性.
泛型
@Test
public void testArrayList(){
Number num = new Integer(1);
System.out.println(num);
ArrayList<Number> list = new ArrayList<Integer>(); //error
ArrayList<? extends Number> list1 = new ArrayList<Number>();
list1.add(new Integer(1));//error
}
这里可以看到,Number对象可以由Integer实例化,令f(A) = ArrayList,如果ArrayList是逆变的,则ArrayList
数组
很容易证明数组是协变的
Number[] numbers = new Integer[3];
由上可以知道,泛型是不变的,数组是协变的,但是有些业务需要泛型进行协变,这时就引入了泛型通配符,下节继续介绍.