Kotlin 中的使用站点差异

mal*_*din 3 generics covariance contravariance generic-variance kotlin

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])
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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])
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我无法理解引擎盖下发生了什么。有人可以解释一下吗?

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

s1m*_*nw1 6

由于您仅以一种方式使用该函数,因此您无论如何都应该使用使用站点差异修饰符来向调用者明确说明您可以添加到以下内容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])
    }
}
Run Code Online (Sandbox Code Playgroud)

此外,由于src实际上用作 aList而不是 a MutableList,因此您应该相应地更喜欢它。因此,您将不再需要out修饰符,因为List已经将其类型参数定义Tout

fun <T> copy(src: List<T>, dst: MutableList<in T>)
Run Code Online (Sandbox Code Playgroud)

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

1) 当您设置dst为 时MutableList<in T>,编译器知道您只会添加T基于src它的类型(在您的示例中为B)。

2) 当您设置src为 时MutableList<out T>,编译器理解您只会添加T及其子类型dst也很好(在这种情况下T将被推断为A好像)。

  • 同意上面@s1m0nw1 的回答中的所有内容。您可能会发现有用的内容是阅读 [关于泛型的官方文档页面](https://kotlinlang.org/docs/reference/generics.html),并注意它们在 **producers** 和 **consumers** 之间的区别. 基本上,会产生标记为“out”的东西(因此不可变列表使用“out”,因为它们只产生值,而不是消耗它们);标记为“in”的东西被消耗了。 (2认同)