Better

业精于勤荒于嬉

Idiomatic Kotlin: variance

Better's Avatar 2019-07-03 Kotlin

  1. 1. 数组
  2. 2. 泛型
    1. 2.1. Java 中的泛型
      1. 2.1.1. declcration sit variance
    2. 2.2. Kotlin 中的泛型
      1. 2.2.1. use-site variance
  3. 3. 函数返回值的协变
    1. 3.1. 参考

variance原文没有怎么看懂,翻译过来就是形变,更抽象。variance和variant都是一个意思。

variance定义了子类型和父类型的在进行一定操作后的结果之间的类型关系。

在面向对象语言中通常会有子类型(subtype)的概念,表示某类是某类的子类。

父类是可以引用子类类型对象的。

1
A item = new B();

假设有A,B两个类,类B继承自类A,用表示继承关系,即B≤A。进行一定的变化操作后(通常是类型引用,数组,泛型),变化操作记作f(⋅)

  1. 如果B≤Af(B)≤f(A),那么操作f(⋅)就是covariance(协变)

  2. 如果B≤Af(A)≤f(B),那么操作f(⋅)就是contrvariance(逆变)

  3. 如果B≤Af(A)f(B)之间没有继承关系,那么操作f(⋅)就是invariance(不变)

数组

1
2
B[] b = new B[]{new B(), new B()};
A[] a = b;

Java 中父类的数组是可以持有子类的数组的,数组支持covarance

1
2
3
val b: Array<B> = arrayOf(B())
// type mismatch
val a: Array<A> = b

Kotlin 中数组是不支持协变的

泛型

泛型在编译时期会检查类型,类型安全机制。

Java 中的泛型

1
2
3
4
// 编译失败
ArrayList<A> a = new ArrayList<B>();
// 编译失败
ArrayList<B> b = new ArrayList<A>();

两种方式都报错incompatiable type,所以泛型是不支持covariance,也不支持contrvariance,是invariance

由于泛型默认情况是是invariance,可以通过一定的方式让泛型支持contrvariance以及covariance

Java 中支持协变的方式是extendssuper关键字。

在泛型的传参处,申明泛型实参类型是受限制的边界类型,即 use-site variance

1
2
3
4
5
6
7
8
9
10
11
12
// covarance
ArrayList<? extends A> array = new ArrayList<B>();
// compile error
array.add(new B());
// ok
arrar.get(0);
// contrvarance
ArrayList<? super B> array = new ArrayList<A>;
// ok
arrary.add(new B);
// compile error
arrary.get(0);
  • corvarince,只能获取元素,Producer Extends

  • contrvariance,只能保存元素,Consumer Super

declcration sit variance

这种方式只是限制类泛型类型的实参类型,只能A或A的子类作为泛型的参数。并不是什么申明处形变。

1
2
3
4
static <T extends A> void printDeclaration(T r) {
System.out.print(r);
}
printDeclaration(new B());

Kotlin 中的泛型

1
2
3
4
5
val boxb: Box<B> = Box<B>()
// type mismatch
val boxa: Box<A> = boxb

class Box<T>

泛型默认是不支持协变的,是invariance

Kotlin 中支持协变的方式是inout关键字。在定义泛型类型类或泛型方法的时候申明支持哪种形变,即Declaration-site variance

  • covariance,只能读取元素并作为返回类型,out

  • contravariance,只能保存元素并作为输入类型,in

covariance use out

out 标记的泛型,不能出现在参数中

1
2
3
4
5
6
7
8
9
10
11
interface Producer<out T> {
fun produce(): T
// compile error
fun input(T t):Boolean
}

class NumberProducer : Producer<Number> {
override fun produce(): Number = Math.random()
}

val producer: Producer<Any> = NumberProducer()

contravariance use in

in标记的泛型不能出现在返回值中。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Consumer<in T> {
fun consume(input: T): Boolean
// compile error
fun consume():T
}

class NumberConsumer : Consumer<Number> {
override fun consume(input: Number): Boolean = true
}

fun checkConsumer(consumer: NumberConsumer) {
val intConsumer: Consumer<Int> = consumer
}

use-site variance

1
2
3
4
5
6
7
8
9
10
11
12
interface Group<T> {
fun setGroup(value: T)
fun getGroup(): T
}

fun doSomething(input1: Group<out Number>, input2: Group<in Number>) {
input1.setGroup(12) // compile error
val outGroup: Number = input1.getGroup()

input2.setGroup(12)
val inGroup: Number = input2.getGroup() // compile error
}

跟 Java 中通配符泛型(generic wildcards)类似,语法上跟简洁了一些。

函数返回值的协变

在重载一个方法时返回类型是可以返回一个更小的范围,可以返回父类返回类型的子类,意即返回值支持协变,corvariance

1
2
3
4
5
6
7
8
9
10
11
public class A {
Object print(String name) {
return "";
}
}
public class B extends A {
@Override
String print(String name) {
return "B overide print()";
}
}

参考

也谈泛型,从 Java 到 Kotlin 之Java篇

深入理解Java与Kotlin的泛型(Generic Type)和型变(Variance

idiomatic kotlin: variance

This article was last updated on days ago, and the information described in the article may have changed.