我最近一直在学习 Kotlin,同时对协变类型有一些疑问。
示例代码在此处。 我有Option
和Option2
都有一个类型参数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 : B
,C()
通过了B
的检查, - 因此
Option(C())
也可以键入为Option<B>
并通过检查, - 好的,lambda 通过了
(Int) -> Option<B>
检查。
健全性检查:如果按如下方式替换第一行怎么办?val func: (Int) -> Option<B> = { Option(C()) as Option<C> }
然后它不会被编译,因为 lambda 中的表达式现在被键入为
Option<C>
这不是Option<B>
的子类型。 - 预计 lambda 将返回
-
opt1.run { Option1(A()) } //return type is Option<A>; why could this compile?
在此示例中,编译器为
T
选择的类型不是B
,而是A
。由于类型参数T
的协方差,允许编译器执行此操作。opt1
Option1<B>
-
Option1<out T>
在T
上是协变的,它允许用任何超类型的B
替换T
,这是允许的,因为对于任何
Z
,例如B : Z
,由于out
修饰符,opt1
也可以被视为Option1<out Z>
,然后编译器可以根据接收方类型Option1<Z>
对调用进行类型检查。 -
T
的替换将是B
的最不常见的超类型,无论X
λ返回Option1<X>
, - λ返回
Option1<A>
, - 找到最不常见的
B
和A
超型, - 鉴于
B : A
,最不常见的超型是A
- 替代
T := A
.
健全性检查:如果按如下方式更改表达式怎么办?
opt1.run { Option1(0) }
它仍然会成功编译,但推断的返回类型将
Option1<Any>
。根据上述情况,这是完全合理的,因为B
和Int
最不常见的超型是Any
。
免责声明:这不是编译器内部的工作方式,但使用这种推理方式,您可能经常得到与编译器结果一致的结果。