考虑以下示例:
import kotlin.reflect.KProperty1
infix fun <T, R> KProperty1<T, R>.test(value: R) = Unit
data class Foo(val bar: Int)
fun main() {
Foo::bar test "Hello"
}
鉴于test
期望类型为value
的{{1}},为什么在这种情况下,如果属性类型为R
,它是否允许我传递{{ 1}}?
答案 0 :(得分:8)
首先,查看接口KProperty1
的声明,即:
interface KProperty1<T, out R> : KProperty<R>, (T) -> R
这里的重要部分是out
-projected类型参数R
,它定义了KProperty1
类型之间的子类型关系,其中R
使用了不同的类型参数。
(1) 即,对于任何Foo
,A
和B
,A : B
({ {1}}是A
)的子类型,
B
。之所以称为协方差,是因为参数化类型彼此之间的关联方式与其类型参数相同。
(2) 接下来,请注意,对于KProperty1<Foo, A> : KProperty1<Foo, B>
和A
中的任何一个,B
都是{{ 1}}可以作为参数传递给任何A : B
类型的参数。扩展功能的接收器参数在这方面与普通参数没有什么不同。
现在,关键部分是编译器运行的类型推断算法。类型推断的目标之一是为每个泛型调用建立静态已知的类型实参,而忽略类型实参。
在调用A
的类型推断期间,编译器需要根据接收者B
的已知类型实际推断Foo::bar test "Hello"
和T
的类型参数( R
和Foo::bar
自变量KProperty1<Foo, Int>
(value
)。
这是通过解决约束系统在内部完成的。我们可以如下模拟此逻辑:
鉴于"Hello"
作为String
传递:
KProperty<Foo, Int>
(因为KProperty<T, R>
是不变的)T := Foo
或其任何超类型作为类型参数T
Int
的协方差:给定 (1) 和 (2) >结合起来,为R
选择R
或它的某些超类型是必须的,以便能够通过Int
并在预期R
的地方传递KProperty<Foo, Int>
,KProperty<Foo, R>
,Int?
,Number
,Number?
鉴于Any
作为Any?
传递:
String
或它的某些超类型用作R
String
(由于 (2) R
,String
,R
,String?
,CharSequence
鉴于对CharSequence?
的两个约束,即它应为Any
或其某些超类型,应为Any?
或其某些超型,编译器会找到最不常见的同时满足这两种要求的类型。此类型为R
。
因此,推断出的类型实参为Int
和String
,使用显式类型实参的调用为:
Any
在IntelliJ IDEA中,可以在非插入调用上使用操作添加显式类型参数来添加推断的类型。
免责声明:这并不完全是编译器内部的工作方式,但是使用这种推理方式,您可能经常会得到与编译器结果一致的结果。
也相关: