我有以下代码:
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>
实例,其中Baz
是Bar
的父类(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
)会导致歧义。也相关:
(基本上是相同的情况,但是在这里有关于类型推断如何工作的更详细的解释)