Kotlin-通用类型参数不受尊重

时间:2019-08-01 23:27:02

标签: generics kotlin type-inference typechecking type-parameter

考虑以下示例:

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}}?

1 个答案:

答案 0 :(得分:8)

首先,查看接口KProperty1的声明,即:

interface KProperty1<T, out R> : KProperty<R>, (T) -> R

这里的重要部分是out-projected类型参数R,它定义了KProperty1类型之间的子类型关系,其中R使用了不同的类型参数。

(1) 即,对于任何FooABA : B({ {1}}是A)的子类型,
B。之所以称为协方差,是因为参数化类型彼此之间的关联方式与其类型参数相同。


(2) 接下来,请注意,对于KProperty1<Foo, A> : KProperty1<Foo, B>A中的任何一个,B都是{{ 1}}可以作为参数传递给任何A : B类型的参数。扩展功能的接收器参数在这方面与普通参数没有什么不同。


现在,关键部分是编译器运行的类型推断算法。类型推断的目标之一是为每个泛型调用建立静态已知的类型实参,而忽略类型实参。

在调用A的类型推断期间,编译器需要根据接收者B的已知类型实际推断Foo::bar test "Hello"T的类型参数( RFoo::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?NumberNumber?
  • 鉴于Any作为Any?传递:

    • 我们必须将String或它的某些超类型用作R
      • 要通过String(由于 (2)
      • 这些超类型的示例是RStringRString?CharSequence

鉴于对CharSequence?的两个约束,即它应为Any或其某些超类型,应为Any?或其某些超型,编译器会找到最不常见的同时满足这两种要求的类型。此类型为R

因此,推断出的类型实参为IntString,使用显式类型实参的调用为:

Any

在IntelliJ IDEA中,可以在非插入调用上使用操作添加显式类型参数来添加推断的类型。


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


也相关: