问题
I have the following code:
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
}
I am trying create extension functions for the KProperty1
class with generics, but my generics are not matching as I'd expect. main()
lists some uses of the two functions, with 3 unexpected results at the end and a description of what I should expect.
回答1:
First, note that KProperty1<T, out R> has an out-projected type parameter R, so an instance of KProperty1<Foo, Bar>
can also be used where a KProperty1<Foo, Baz>
is expected, where Baz
is a supertype of Bar
, such as Any
.
This is exactly what happens when you call eq
with types that don't quite match: a more common supertype is used so that the call resolves correctly.
For example, this call:
Person::age eq 1.2f
is effectively equivalent to:
Person::age.eq<Person, Any>(1.2f)
You can even transform it to this form automatically be applying Replace infix call with ordinary call and then Add explicit type arguments.
So whenever the types don't match exactly, a common supertype is chosen for R
. Currently, there's no proper way to tell the compiler it should not fall back to the supertype (see this Q&A).
The last call is a side effect of how type inference currently works: it seems like the compiler has to choose one of the overloads before it decides on whether it is compatible for nullability. If you replace null
with (null as String?)
it works. Please file an issue for this at kotl.in/issue.
In general, it is preferable not to mix the generic signatures with non-generic ones because of possible generic substitutions that lead to ambiguity. In your case, a substitution R := KProperty1<T, Foo>
(i.e. using eq
on a property of this type) would lead to ambiguity.
Also relevant:
Kotlin - Generic Type Parameters Not Being Respected
(basically the same case, but with a more detailed explanation of how type inference works here)
来源:https://stackoverflow.com/questions/52717977/generic-extensions-of-kproperty1-in-kotlin