5

我正在玩反射,我遇到了这个问题。通过::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,它返回一个带有通配符的变体类型,javaClass而是一个getClass带有特定类型的强制转换。尽管如此,我还是不明白为什么我需要一个协变 KClass,因为它限制我只生成类型,因为还有其他方法可以在运行时访问确切的类并自由使用它,我想知道更多immediate::class应该按设计返回一个不变的类型。

4

1 回答 1

3

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

例子:

open class Base
class Derived : Base()

fun someBase(): Base = Derived()

val kClass = someBase()::class

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

键入someBase()::class为 invariantKClass<Base>是完全不正确的,事实上,评估这个表达式的实际结果是KClass<Derived>.

为了解决这种可能的不一致(这将导致破坏类型安全),所有绑定的类引用都是协变的:someBase()::classis KClass<out Base>,这意味着在运行时someBase()可能是 的子类型Base,因此这可能是 的子类型的类标记Base

当然,未绑定的类引用不是这种情况:当您使用 时Base::class,您肯定知道它是其某些子类型的类标记,Base而不是其某些子类型的标记,因此它是不变的KClass<Base>

于 2017-08-20T13:17:51.350 回答