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

时间:2019-04-19 16:50:30

标签: generics kotlin covariant generic-variance

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

示例代码在这里。 我有OptionOption2都具有类型参数T和扩展名run

我可以理解run中的前两个validation(),因为它们表现得像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>
}

1 个答案:

答案 0 :(得分: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>构造函数调用需要接受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的协变变量,它可以用T的任何超类型替换B

      之所以允许这样做,是因为对ZB : Z这样的opt1Option1<out Z>也可以归为out,这要归功于Option1<Z>修饰符,然后编译器可以根据接收者类型T对调用进行类型检查。

    • B的替代将是X以及任何Option1<X>的最不常见的超类型,以使lambda返回Option1<A>

    • lambda返回B
    • 找到AB : A的最不常见超类型,
    • 假设A,最不常见的超类型是T := A
    • 替换opt1.run { Option1(0) }


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

    Option1<Any>

    它仍将成功编译,但推断的返回类型将为B。根据上述情况,这是完全合理的,因为IntAny的最不常见超类型是# unk to json & xml else: with open(filePath) as u: fLine = u.readline() #This is only reading the first line. uStr = u.read() if '<' in fLine: time = strftime('%Y%b%d %H%M', gmtime()) fName = fileName + ' ' + time + ".xml" with open(fName, 'w') as x: x.write(uStr) elif '{' in fLine: time = strftime('%Y%b%d %H%M', gmtime()) fName = fileName + ' ' + time + ".json" with open(fName, 'w') as j: j.write(uStr)


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