Kotlin 中的使用位置差异

Use-Site variance in 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])
    }
}

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

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

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

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

我的问题是,如果更新函数参数 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])
    }
}

在这种情况下,我无法理解hood.Can下发生了什么,请解释一下?

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

中的不可变列表

由于您仅以一种方式使用该函数,因此您无论如何都应该使用 use-site variance modifiers 以使调用者清楚地知道您可以添加到 dst 并从 [=13] 获取数据=]:

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> 时,编译器知道您只会向它添加基于 srcT 类型(在您的示例中是 B).

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

outin:

对称

in keyword, I am saying that src must be of exactly type T but destination can be of type T or any super type of T

所以现在你说 src 必须是 T 类型的 MutableListT 的任何子类型,而 dst 必须是MutableList 类型 T.

所以当你有 l1: MutableList<B>l2: MutableList<A> 时,编译器将 copy(l1, l2) 中的类型参数推断为 copy<A>(l1, l2),并进行类型检查: MutableList<B>MutableList<out A>.

的子类型

因为您只在 src 上使用 out 兼容的操作,并且在 dst 上只使用 in 兼容的操作,正如@s1m0nw1 所说的那样非常有意义包括两个修饰符。