Java中的协变与逆变

参考:(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 是ArrayList的父类型;如果是协变的,则ArrayList是ArrayList的子类型;如果不变,二者没有相互继承关系,从上面的代码报错可以看出,泛型是不变的.

数组

很容易证明数组是协变的

Number[] numbers = new Integer[3];

由上可以知道,泛型是不变的,数组是协变的,但是有些业务需要泛型进行协变,这时就引入了泛型通配符,下节继续介绍.

原文地址:https://www.cnblogs.com/liuzhidao/p/13744794.html