我有以下代码:

import kotlin.reflect.KProperty1


infix fun <T, R> KProperty1<T, R>.eq(value: R) {
    println(this.name + " = $value")
}

infix fun <T, R> KProperty1<T, R>.eq(value: KProperty1<T, R>) {
    println(this.name + " = " + value.name)
}

data class Person(val age: Int, val name: String, val surname: String?)

fun main() {
    Person::age eq 1  // Valid. First function
    Person::name eq "Johan"  // Valid. First function
    Person::name eq Person::name  // Valid. Second function
    Person::age eq 1.2f  // Valid, but it shouldn't be. First function
    Person::age eq Person::name  // Valid, but it shouldn't be. First function
    Person::surname eq null  // Invalid, but it should be. Second function, but it should be first
}

我正在尝试使用泛型为KProperty1类创建扩展功能,但是我的泛型与我期望的不匹配。 main()列出了这两个函数的一些用法,最后有3个意外结果,并描述了我应该期望的结果。

最佳答案

首先,请注意 KProperty1<T, out R> 具有 out -projected type parameter R ,因此也可以在需要KProperty1<Foo, Bar>的情况下使用KProperty1<Foo, Baz>实例,其中BazBar的父类(super class)型,例如Any

当您使用不太匹配的类型调用eq时,就会发生这种情况:使用了更常见的父类(super class)型,以便调用能够正确解析。

例如,此调用:

Person::age eq 1.2f

实际上等效于:
Person::age.eq<Person, Any>(1.2f)

您甚至可以通过以下方式自动将其转换为这种形式:应用普通调用替换infix调用,然后添加显式类型参数。

因此,只要类型不完全匹配,就会为R选择一个通用的父类(super class)型。当前,没有正确的方法告诉编译器它不应该退回到父类型(请参阅this Q&A)。

最后一个调用是类型推断当前工作方式的副作用:似乎编译器必须先选择一种重载,然后才能确定其是否兼容可空性。如果将null替换为(null as String?),则可以使用。请为此向kotl.in/issue提交问题。

通常,最好不要将通用签名与非通用签名混合使用,因为可能导致通用替换而导致歧义。在您的情况下,替换R := KProperty1<T, Foo>(即在此类型的属性上使用eq)会导致歧义。

也相关:
  • Kotlin - Generic Type Parameters Not Being Respected

    (基本上是相同的情况,但是在这里有关于类型推断如何工作的更详细的解释)
  • 10-07 17:26