I'm playing with reflection and I came out with this problem. When using bound class reference via the ::class
syntax, I get a covariant KClass type:
fun <T> foo(entry: T) {
with(entry::class) {
this // is instance of KClass<out T>
}
}
As I could learn from the docs, this will return the exact type of the object, in case it is instance of a subtype of T
, hence the variance modifier.
However this prevents retrieving properties declared in the T
class and getting their value (which is what I'm trying to do)
fun <T> foo(entry: T) {
with(entry::class) {
for (prop in memberProperties) {
val v = prop.get(entry) //compile error: I can't consume T
}
}
}
I found that a solution is using javaClass.kotlin
extension function on the object reference, to get instead the invariant type:
fun <T> foo(entry: T) {
with(entry.javaClass.kotlin) {
this // is instance of KClass<T>
}
}
This way, I get both the exact type at runtime and the possibility to consume the type.
Interestingly, if I use a supertype instead of a generic, with the latter method I still get access to the correct type, without the need of variance:
class Derived: Base()
fun foo(entry: Base) {
with(entry.javaClass.kotlin) {
println(this == Derived::class)
}
}
fun main(args: Array<String>) {
val derived = Derived()
foo(derived) // prints 'true'
}
If I got it correct, ::class
is equal to calling the java getClass
, which returns a variant type with a wildcard, while javaClass
is a getClass
with a cast to the specific type.
Still, I don't get why would I ever need a covariant KClass, when it limits me to only produce the type, given that there are other ways to access the exact class at runtime and use it freely, and I wonder if the more immediate ::class
should return an invariant type by design.
The reason for covariance in bound ::class
references is, the actual runtime type of an object the expression is evaluated to might differ from the declared or inferred type of the expression.
Example:
open class Base
class Derived : Base()
fun someBase(): Base = Derived()
val kClass = someBase()::class
The expression someBase()
is typed as Base
, but at runtime it's a Derived
object that it gets evaluated to.
Typing someBase()::class
as invariant KClass<Base>
is simply incorrect, in fact, the actuall result of evaluating this expression is KClass<Derived>
.
To solve this possible inconsistency (that would lead to broken type-safety), all bound class references are covariant: someBase()::class
is KClass<out Base>
, meaning that at runtime someBase()
might be a subtype of Base
, and therefore this might be a class token of a subtype of Base
.
This is, of course, not the case with unbound class references: when you take Base::class
, you know for sure that it's the class token of Base
and not of some of its subtypes, so it's invariant KClass<Base>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With