我最近在学习 Kotlin,同时有一些关于协变类型的问题。
示例代码在这里。
我有Option
和Option2
两者都有一个类型参数 T
和一个 run
扩展名。
我能理解前两个run
在validation()
,因为它们的行为与 Java 相同。
但是为什么第三行可以编译呢? Option<T>
在 T
中不变 。我们无法通过Option<C>
实例进入 where 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>
}
最佳答案
-
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
上的协变,允许替换T
具有任何父类(super class)型B
,这是允许的,因为对于任何
Z
这样B : Z
,opt1
也可以视为Option1<out Z>
感谢out
修饰符,然后编译器可以根据接收者类型Option1<Z>
对调用进行类型检查.T
的替代将是B
最不常见的父类(super class)型等等X
这样 lambda 返回Option1<X>
,- lambda 返回
Option1<A>
, - 找到
B
的最不常见的父类(super class)型和A
, - 鉴于
B : A
,最不常见的父类(super class)型是A
- 替代
T := A
.
健全性检查:如果按如下方式更改表达式会怎样?
opt1.run { Option1(0) }
它仍然会成功编译,但推断的返回类型将是
Option1<Any>
。根据上面的说法,这是完全合理的,因为B
是最不常见的父类(super class)型。和Int
是Any
. -
免责声明:这不是编译器内部工作的方式,但使用这种推理方式您可能经常会得到与编译器的结果一致的结果。
关于generics - Kotlin 泛型 : counterintuitive type inference and checking with out keyword,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55765128/