绑定类引用返回协变类型的目的是什么?

时间:2017-08-20 12:56:41

标签: kotlin kotlin-reflect

我正在玩反思,我出现了这个问题。当通过::class语法使用绑定类引用时,我得到一个协变的KClass类型:

fun <T> foo(entry: T) {
    with(entry::class) {
       this // is instance of KClass<out T>
    }
}

正如我可以从文档中学到的,这将返回对象的确切类型,以防它是T子类型的实例,因此是方差修饰符。 但是,这会阻止检索T类中声明的属性并获取它们的值(这是我尝试做的事情)

fun <T> foo(entry: T) {
    with(entry::class) {
       for (prop in memberProperties) {
           val v = prop.get(entry) //compile error: I can't consume T
       }
    }
}

我发现解决方案是在对象引用上使用javaClass.kotlin扩展函数,而不是使用不变类型:

fun <T> foo(entry: T) {
    with(entry.javaClass.kotlin) {
       this // is instance of KClass<T>
    }
}

这样,我在运行时获得了确切的类型,并且可以使用该类型。

有趣的是,如果我使用超类型而不是泛型,使用后一种方法我仍然可以访问正确的类型,而不需要变化:

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

如果我弄错了,::class等于调用java getClass,它返回带有通配符的变体类型,而javaClassgetClass,带有强制转换为特定类型。 尽管如此,我还不知道为什么我需要一个协变的KClass,因为它限制我只生成类型,因为有其他方法可以在运行时访问确切的类并自由使用它,我想知道是否更直接的::class应该按设计返回不变类型。

1 个答案:

答案 0 :(得分:3)

绑定::class引用中协方差的原因是,表达式计算的对象的实际运行时类型可能与表达式的声明或推断类型不同。

示例:

open class Base
class Derived : Base()

fun someBase(): Base = Derived()

val kClass = someBase()::class

表达式someBase()的类型为Base,但在运行时它是一个Derived对象,它被评估为。

someBase()::class键入KClass<Base>是不正确的,事实上,评估此表达式的实际结果是KClass<Derived>

要解决这种可能的不一致性(这会导致类型安全性受损),所有绑定类引用都是协变的:someBase()::classKClass<out Base>,这意味着在运行时someBase()可能是Base的子类型,因此这可能是Base子类型的类标记。

当然,这与未绑定的类引用不同:当你使用Base::class时,你肯定知道它是Base的类标记而不是它的某些子类,所以它是不变的KClass<Base>