理解协变、逆变、不变 ,kotlin in out关键字

148 阅读2分钟

前景知识

liskov替换原则,Lsp原则————所有使用父类对象的地方都可以透明的使用子类。

1.lsp原则是面对对象语言的设计原则。

  • 引申出了的多态

     Object s = "123"
     Number n = 123
     我们声明一个父类对象,然后用子类进行实例化
    
  • 子类继承父类的方法,入参更宽松

  • 子类继承父类的方法,出参更严格

其中2和3有点绕,也都是基于【所有使用父类对象的地方都可以透明的使用子类】这一核心。

2.协变,逆变,不变 本身是用于描述继承关系的转化。

A是B的子类,经过f(x)类型转换之后,f(A)是f(B)的子类,就认为是协变,反之称之为逆变器,如果二者没关系,是不变。

具体怎么判断A是B的子类,就是使用上面前景知识的多态转化 比如:

Object s  = "123" ----> 我们说String是Object的子类

Object[] arr = new String[];  ----> 编译通过,我们认为数组这种转换关系是协变的。

List<Object> list = new ArrayList<String>(); ----> 编译不通过,我们认为list这种转换关系是不变的。

泛型本身是不变的,但是很多时候需要实现逆变、协变,要想达到继承关系,需要使用关键字 extends, super

List<? extends Number> list = new ArrayList<Integer>();//编译通过
List<? extends Number> list2 = new ArrayList<Object>(); //编译报错
list.add(123); //编译报错
Number s1 = list.get(0); //编译通过
Integer s2 = (Integer) list.get(0); //编译通过
	
//编译通过
List<? super Number> list1 = new ArrayList<Object>(); //编译通过
list1.add(123); //编译通过
list1.add(456.0); //编译通过
Number number = list1.get(0); //编译报错

首先 IntegerNumber 的子类, NumberObject 的子类。 但是现在:

List<Integer> 是 List<? extends Number> 的子类,因此说,extends实现了协变
ArrayList<Object> 是 List<? super Number> 的子类,因此说 super 实现了逆变

3.kotlin in out

先说结论: 二者都是修饰泛型的,因为泛型是不变的,我们想实现协变,逆变,必须像java一样增加关键字

out producer 返回值,实现协变
in consumer 入参,实现逆变
* 通配符,匹配

使用原则:

如果泛型只用于返回值,就用out
如果泛型只用于入参数,就用in
如果泛型同时使用于返回值和入参,用 * 通配符吧。

这个概念理解的前提是:

out实现协变---- 返回值更加严格
in实现逆变 ---- 入参更加宽松