Kotlin - 不遵守通用类型参数
Kotlin - Generic Type Parameters Not Being Respected
考虑以下示例:
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
需要类型 R
的 value
,为什么在这种情况下,属性 类型是 Int
,它允许我要传一个String
?
首先看一下接口KProperty1
的声明,即:
interface KProperty1<T, out R> : KProperty<R>, (T) -> R
这里的重要部分是 out
-projected 类型参数 R
,它定义了 KProperty1
类型与用于 R
的不同类型参数之间的子类型关系。
(1) 即,对于任何 Foo
、A
和 B
这样的A : B
(A
是 B
的子类型),
KProperty1<Foo, A> : KProperty1<Foo, B>
。这称为协变,因为参数化类型以与其类型参数相同的方式相互关联。
(2) 接下来,请注意对于任何 A
和 B
这样 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
或其任何超类型作为类型参数 R
- 这是因为
R
的协方差:给定 (1) 和 (2) 组合,为 R
选择 Int
或它的某些超类型对于能够传递 KProperty<Foo, Int>
其中 KProperty<Foo, R>
预计
- 这些超类型的示例是
Int?
、Number
、Number?
、Any
、Any?
鉴于 String
作为 R
传递:
- 我们必须使用
String
或其某些超类型作为 R
- 这是能够通过
String
所必需的,其中由于 (2)[=137,预计 R
=]
- 这些超类型的示例是
String?
、CharSequence
、CharSequence?
、Any
、Any?
给定 R
的两个约束,即它应该是 Int
或其某些超类型,并且它应该是 String
或其某些超类型,编译器发现满足两者的最不常见的类型。这种类型是 Any
.
因此,推断的类型参数是 T := Foo
和 R := Any
,带有显式类型参数的调用将是:
Foo::bar.test<Foo, Any>("Hello")
在 IntelliJ IDEA 中,您可以在非中缀调用上使用操作添加显式类型参数来添加推断类型。
免责声明:这并不是编译器内部的确切工作方式,但使用这种推理方式您可能经常会得到与编译器结果一致的结果。
也相关:
考虑以下示例:
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
需要类型 R
的 value
,为什么在这种情况下,属性 类型是 Int
,它允许我要传一个String
?
首先看一下接口KProperty1
的声明,即:
interface KProperty1<T, out R> : KProperty<R>, (T) -> R
这里的重要部分是 out
-projected 类型参数 R
,它定义了 KProperty1
类型与用于 R
的不同类型参数之间的子类型关系。
(1) 即,对于任何 Foo
、A
和 B
这样的A : B
(A
是 B
的子类型),KProperty1<Foo, A> : KProperty1<Foo, B>
。这称为协变,因为参数化类型以与其类型参数相同的方式相互关联。
(2) 接下来,请注意对于任何 A
和 B
这样 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
或其任何超类型作为类型参数R
- 这是因为
R
的协方差:给定 (1) 和 (2) 组合,为R
选择Int
或它的某些超类型对于能够传递KProperty<Foo, Int>
其中KProperty<Foo, R>
预计 - 这些超类型的示例是
Int?
、Number
、Number?
、Any
、Any?
- 这是因为
- 我们必须使用
鉴于
String
作为R
传递:- 我们必须使用
String
或其某些超类型作为R
- 这是能够通过
String
所必需的,其中由于 (2)[=137,预计R
=] - 这些超类型的示例是
String?
、CharSequence
、CharSequence?
、Any
、Any?
- 这是能够通过
- 我们必须使用
给定 R
的两个约束,即它应该是 Int
或其某些超类型,并且它应该是 String
或其某些超类型,编译器发现满足两者的最不常见的类型。这种类型是 Any
.
因此,推断的类型参数是 T := Foo
和 R := Any
,带有显式类型参数的调用将是:
Foo::bar.test<Foo, Any>("Hello")
在 IntelliJ IDEA 中,您可以在非中缀调用上使用操作添加显式类型参数来添加推断类型。
免责声明:这并不是编译器内部的确切工作方式,但使用这种推理方式您可能经常会得到与编译器结果一致的结果。
也相关: