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
中的不可变列表
答案 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
类型的T
或T
的任何子类型,而dst
必须是{{ 1}}的类型完全是MutableList
。
因此,当您拥有T
和l1: MutableList<B>
时,编译器将l2: MutableList<A>
中的type参数推断为copy(l1, l2)
,并进行类型检查:copy<A>(l1, l2)
是子类型的MutableList<B>
。
因为您仅在MutableList<out A>
上使用out
兼容的操作,而在src
上仅使用in
兼容的操作,如@ s1m0nw1所说,包括这两个修饰符。