【问题标题】:Use-Site variance in KotlinKotlin 中的使用站点差异
【发布时间】:2019-04-08 15:55:14
【问题描述】:
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&lt;T&gt;, dst: MutableList&lt;in T&gt;) 请注意 in 关键字,我是说 src 必须完全是 T 但目的地可以是 type T任何超类型的 T

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

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])
    }
}

在这种情况下,我无法理解幕后发生的事情。谁能解释一下吗?

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

【问题讨论】:

    标签: generics kotlin covariance contravariance generic-variance


    【解决方案1】:

    由于您仅以一种方式使用该函数,因此无论如何您都应该使用使用站点方差修饰符,以使调用者清楚您可以添加到 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&lt;A&gt;,一次使用MutableList&lt;B&gt;,问题实际上会发生。编译器无法推断copy 的类型应该是A 还是B。要解决此问题,您需要提供更多信息:

    1) 当您将dst 设置为MutableList&lt;in T&gt; 时,编译器知道您只会向其添加基于srcT 类型(在您的示例中为B)。

    2) 当您将src 设置为MutableList&lt;out T&gt; 时,编译器知道您只会将T 及其子类型添加到dst,这也很好(在这种情况下T 将被推断为A 虽然)。

    【讨论】:

    • 同意上面@s1m0nw1 回答中的所有内容。阅读official documentation page about generics 可能会对您有所帮助,并注意它们在生产者消费者 之间的区别。基本上,会产生标记为out 的东西(因此不可变列表使用out,因为它们只产生值,而不是消耗它们);标记为in 的东西被消耗了。
    • 谢谢@s1m0nw1。 in this case T will be inferred as A though 现在知道了。
    【解决方案2】:

    out 在这里与in 对称工作:

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

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

    因此,当您有l1: MutableList&lt;B&gt;l2: MutableList&lt;A&gt; 时,编译器将copy(l1, l2) 中的类型参数推断为copy&lt;A&gt;(l1, l2),并进行类型检查:MutableList&lt;B&gt;MutableList&lt;out A&gt; 的子类型。

    因为您只对src 使用out 兼容操作,并且只对dst 使用in 兼容操作,正如@s1m0nw1 所说,包含这两个修饰符非常有意义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-06
      • 2020-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-06
      • 1970-01-01
      相关资源
      最近更新 更多