Kotlin中的泛型:协变与逆变

协变与逆变

现在假设存在类A和类B,以及泛型类LIst<A>和泛型类LIst<B>,则协变和逆变的定义如下:

  • 协变

    如果A是B的子类,且List<A>是List<B>的子类,那么可以说泛型List<T>是协变的

  • 逆变
    如果A是B的子类,且List<B>是List<A>的子类,那么可以说泛型List<T>是逆变的

Java中的泛型

Java中的泛型不是协变的,即List<String>并不是List<Object>的子类,这样就会给一些操作带来了不必要的麻烦,例如:

List<Object> list = new ArrayList<>();
List<String> strings = new ArrayList<>();
list.addAll(strings)

因为List<String>不是List<Object>的子类,理论上我们调用 addAll 方法是会编译报错的。但实际上并不会,原因是Java使用通配符类型来实现了协变。

查看Collections<E>类的源码可以发现addAll方法的参数类型实际上是Collection<? extends E>,即对于所有E的子类型其对应的泛型类都是Collection<? extends E>的子类型。
上述示例中,因为Collection<String>是Collection<? extends Object>的子类,所以可以成功调用addAll方法。

同理,Java中也可以使用通配符类型实现逆变,对于所有E的父类型其对应的泛型类都是Collection<? super E>的子类型。

Kotlin中的泛型

Kotlin泛型的声明式协变和逆变

假设对于List<E>泛型类,仅包含输出类型E的接口,那么我们实际上输出任何E的子类型的实例都是安全的。例如:

interface List<E> {
    fun get(index: Int) : E
}

那么List<String>就是List<Object>的子类型,即可以将List<String>类型的对象赋值给List<Object>类型的变量,因为赋值给List<Object>类型的变量后实际只会输出String类型的实例,而String类型是Object类型的子类,所以是安全的。

上述接口在Kotlin中需要使用 out 关键字来表示该泛型是协变的,如下所示:

interface List<out E> {
    fun get(index: Int) : E
}

out 关键字即表示该泛型类型仅仅包含输出类型E的接口。

同理,假设List<E>泛型仅包含输入类型E的接口,那么实际上输入任何类型E的子类型的实例都是安全的,Kotlin中使用 in 关键字来表示该泛型是逆变的,如下所示:

interface List<in E> {
    fun add(item: E)
}

此种情况下,List<Object>就是List<String>的子类型,即可以将List<Object>类型的对象赋值给List<String>类型的变量,因为赋值给List<String>类型的变量后只能输入String类型的实例,而String类型是Object类型的子类,所以是安全的。

实际上来说,我们知道List接口不能只支持输入接口或者只支持输出接口,例如List接口需要同时支持get操作和add操作,那么List泛型就不能在类的声明处用in或者out关键字来声明协变或者逆变。对于此种情况,Kotlin中还支持使用处协变和逆变,即类型投影。

Kotlin泛型的类型投影

interface List<E> {
    fun add(item: E)
    fun get(index: Int) : E
}

fun copy(list1: List<out Any>, list2: List<Any>) {
    //copy items from list1 to list2
}

如上所示,copy 函数从 list1 中拷贝所有的元素到 list2 中,对于 list1 列表该函数只需要读取元素,因此可以在参数列表中使用 out 关键字声明 list1 是协变的,即在copy函数中对于 list1的操作只能是读取元素,而不能向 list1 写入任何元素。

同理,如果一个函数只能对某个参数进行写入操作,那么就可以在参数列表中使用 in 关键字来声明。例如:

fun fill(list: List<in String>, str: String) {
    //fill list with str
}
posted @ 2023-07-07 14:28  jqc  阅读(94)  评论(0编辑  收藏  举报