Kotlin 泛型:违反直觉的类型推断和检查 out 关键字



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

示例代码在此处。 我有OptionOption2都有一个类型参数T和一个run扩展名。

我可以理解validation()中的前两个run,因为它们的行为就像Java一样。 但是为什么第三行会编译呢?Option<T>T中是不变的。我们不能Option<C>实例传递到预期Option<B>的位置。

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

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>
}
  • 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>构造函数调用需要接受一个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>这不是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上是协变的,它允许用任何超类型的B替换T

      这是允许的,因为对于任何Z,例如B : Z,由于out修饰符,opt1也可以被视为Option1<out Z>,然后编译器可以根据接收方类型Option1<Z>对调用进行类型检查。

    • T的替换将是B的最不常见的超类型,无论Xλ返回Option1<X>

    • λ返回Option1<A>
    • 找到最不常见的BA超型,
    • 鉴于B : A,最不常见的超型是A
    • 替代T := A.


    健全性检查:如果按如下方式更改表达式怎么办?

    opt1.run { Option1(0) }
    

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


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

最新更新