3

我最近一直在学习 Kotlin,同时对协变类型有一些疑问。

示例代码在这里。我有Option并且Option2都有一个类型参数T和一个run扩展名。

我可以理解 中的前两个runvalidation()因为它们的行为类似于 Java。但是为什么第三行编译?Option<T>在 中是不变T。我们不能将Option<C>实例传递到Option<B>预期的位置。

在我添加一个out关键字之后T,现在它们都可以编译了。为什么?

open class A
open class B : A()
open class C : B()


class Option<T>(val item: T)

fun <T> Option<T>.run(func: (Int) -> Option<T>): Option<T> = func(1)


class Option1<out T>(val item: T) //out keyword

fun <T> Option1<T>.run(func: (Int) -> Option1<T>): Option1<T> = func(1)


fun validation() {
    val opt: Option<B> = Option(B())
    opt.run { Option(A()) } //won't compile as expected
    opt.run { Option(B()) } //return type is Option<B>
    opt.run { Option(C()) } //return type is Option<B>; why could this compile?

    val opt1: Option1<B> = Option1(B())
    opt1.run { Option1(A()) } //return type is Option<A>; why could this compile?
    opt1.run { Option1(B()) } //return type is Option<B>
    opt1.run { Option1(C()) } //return type is Option<B>
}
4

1 回答 1

4
  • opt.run { Option(C()) } //return type is Option<B>; why could this compile?

    在这里,您可以通过将调用分解为分别进行类型检查的两行来近似行为如下:

    val func: (Int) -> Option<B> = { Option(C()) }
    opt.run(func)
    

    第一行是正确的,因为:

    • 预计 lambda 会返回Option<B>(与 完全相同B,因为Option是不变的),
    • 所以Option(item: T): Option<T>构造函数调用需要接受 a B,
    • 传递的参数是C(),
    • 因为C : BC()通过了存在的检查B
    • 所以Option(C())也可以输入为Option<B>并通过检查,
    • 好的,lambda 通过了(Int) -> Option<B>.


    完整性检查:如果您将第一行替换如下?

    val func: (Int) -> Option<B> = { Option(C()) as Option<C> }
    

    然后它不会被编译,因为 lambda 中的表达式现在被键入为Option<C>which 不是Option<B>.


  • opt1.run { Option1(A()) } //return type is Option<A>; why could this compile?

    在此示例中,编译器选择的类型T不是B,而是A。由于类型参数的协变,编译器可以这样做T

    • opt1Option1<B>
    • Option1<out T>在 上是协变的T,它允许T用 的任何超类型替换B

      这是允许的,因为对于任何Z这样的B : Z,opt1也可以被视为Option1<out Z>感谢out修饰符,然后编译器可以针对接收器类型对调用进行类型检查Option1<Z>

    • 的替代品T将是 lambda 返回的最不常见的BX类型Option1<X>

    • lambda 返回Option1<A>,
    • B找到and的最不常见的超类型A
    • 鉴于此B : A,最不常见的超类型是A
    • 替代品T := A


    健全性检查:如果您按如下方式更改表达式会怎样?

    opt1.run { Option1(0) }
    

    它仍然会成功编译,但推断的返回类型将是Option1<Any>. 根据上述内容,这是完全合理的,因为Band的最不常见的超类型IntAny


免责声明:这不是编译器内部的工作方式,但使用这种推理方式,您可能经常得到与编译器结果一致的结果。

于 2019-04-19T17:46:47.637 回答