Kotlin中的使用站点差异

时间:2018-11-05 09:47:13

标签: generics kotlin covariance contravariance generic-variance

open class A
class B: A()

fun <T> copy(src: MutableList<T>, dst: MutableList<T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

对于上面提到的代码,我了解到copy function期望两个类型的参数均为完全相同的类型。稍作修改copy(src: MutableList<T>, dst: MutableList<in T>),注意 in 关键字,我是说src 必须完全是T 类型,但目的地可以是type T T的任何超类型

对于上面修改的代码,我可以调用以下方法,

fun main(args: Array<String>) {
    val l1 = mutableListOf(B(), B())
    val l2 = mutableListOf<A>()
    copy(l1, l2)
} // main

如果我从目的地(理解)中删除了 copy(l1, l2) ,则上述 in 将不起作用。

我的问题是,如果更新函数参数src以接受列表的out投影,我可以无任何错误地调用该函数。例如

fun <T> copy(src: MutableList<out /*notice out here*/ T>, dst: MutableList<T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

在这种情况下,我无法理解幕后发生的事情。有人可以解释一下吗?

请注意,这只是本书中的一个示例。我知道我可以使用List代替src中的不可变列表

2 个答案:

答案 0 :(得分:4)

由于您仅以一种方式使用该函数,因此无论如何都应使用使用地点差异修饰符,以明确告知调用者您可以将其添加到dst并从src获取数据:

fun <T> copy(src: MutableList<out T>, dst: MutableList<in T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

此外,由于src实际上用作List而不是MutableList,因此您应该相应地使用它。结果,您不再需要out修饰符,因为List仅将其类型参数T定义为out

fun <T> copy(src: List<T>, dst: MutableList<in T>)

回答您的问题:当您在主目录中使用两个不同类型的列表调用copy时,实际上会发生此问题,一次使用MutableList<A>,一次使用MutableList<B>。编译器无法推断copy的类型是A还是B。要解决此问题,您需要提供更多信息:

1)将dst设置为MutableList<in T>时,编译器知道您将仅基于T添加src类型(在您的示例中为{ {1}}。

2)将B设置为src时,编译器了解到,您只会将MutableList<out T>及其子类型添加到T中,这也很好(在此情况dst将被推断为T)。

答案 1 :(得分:1)

out在这里与in对称工作:

  

在关键字中,我说的是src必须完全是T类型,但是destination可以是T类型或T的任何超级类型

所以现在您说src必须是MutableList类型的TT的任何子类型,而dst必须是{{ 1}}的类型完全是MutableList

因此,当您拥有Tl1: MutableList<B>时,编译器将l2: MutableList<A>中的type参数推断为copy(l1, l2),并进行类型检查:copy<A>(l1, l2)是子类型的MutableList<B>

因为您仅在MutableList<out A>上使用out兼容的操作,而在src上仅使用in兼容的操作,如@ s1m0nw1所说,包括这两个修饰符。