绑定 class 引用 return 协变类型的目的是什么?
What is the purpose of having bound class reference return a covariant type?
我在玩反射,结果遇到了这个问题。通过 ::class
语法使用绑定 class 引用时,我得到一个协变 KClass 类型:
fun <T> foo(entry: T) {
with(entry::class) {
this // is instance of KClass<out T>
}
}
正如我从文档中了解到的那样,这将 return 对象的确切类型,以防它是 T
子类型的实例,因此是方差修饰符。
但是,这会阻止检索在 T
class 中声明的属性并获取它们的值(这是我正在尝试做的)
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
,其中 return 是带有通配符的变体类型,而 javaClass
是一个 getClass
并转换为特定类型。
尽管如此,我还是不明白为什么我需要协变 KClass,因为它限制我只能生成类型,因为还有其他方法可以在运行时访问确切的 class 并自由使用它,而且我想知道更直接的 ::class
是否应该 return 设计为不变类型。
绑定 ::class
引用中协变的原因是,表达式被评估为的对象的实际运行时类型可能与表达式的声明或推断类型不同。
示例:
open class Base
class Derived : Base()
fun someBase(): Base = Derived()
val kClass = someBase()::class
表达式 someBase()
被键入为 Base
,但在运行时它是一个 Derived
对象,它被评估为。
将 someBase()::class
键入为不变量 KClass<Base>
是完全错误的,事实上,计算此表达式的实际结果是 KClass<Derived>
.
为了解决这种可能的不一致(这会导致破坏类型安全),所有绑定的 class 引用都是协变的:someBase()::class
是 KClass<out Base>
,这意味着在运行时 someBase()
可能是 Base
的子类型,因此这可能是 Base
.
子类型的 class 标记
当然,未绑定的 class 引用不是这种情况:当你使用 Base::class
时,你肯定知道它是 [=13= 的 class 标记] 而不是它的一些子类型,所以它是不变的 KClass<Base>
.
我在玩反射,结果遇到了这个问题。通过 ::class
语法使用绑定 class 引用时,我得到一个协变 KClass 类型:
fun <T> foo(entry: T) {
with(entry::class) {
this // is instance of KClass<out T>
}
}
正如我从文档中了解到的那样,这将 return 对象的确切类型,以防它是 T
子类型的实例,因此是方差修饰符。
但是,这会阻止检索在 T
class 中声明的属性并获取它们的值(这是我正在尝试做的)
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
,其中 return 是带有通配符的变体类型,而 javaClass
是一个 getClass
并转换为特定类型。
尽管如此,我还是不明白为什么我需要协变 KClass,因为它限制我只能生成类型,因为还有其他方法可以在运行时访问确切的 class 并自由使用它,而且我想知道更直接的 ::class
是否应该 return 设计为不变类型。
绑定 ::class
引用中协变的原因是,表达式被评估为的对象的实际运行时类型可能与表达式的声明或推断类型不同。
示例:
open class Base
class Derived : Base()
fun someBase(): Base = Derived()
val kClass = someBase()::class
表达式 someBase()
被键入为 Base
,但在运行时它是一个 Derived
对象,它被评估为。
将 someBase()::class
键入为不变量 KClass<Base>
是完全错误的,事实上,计算此表达式的实际结果是 KClass<Derived>
.
为了解决这种可能的不一致(这会导致破坏类型安全),所有绑定的 class 引用都是协变的:someBase()::class
是 KClass<out Base>
,这意味着在运行时 someBase()
可能是 Base
的子类型,因此这可能是 Base
.
当然,未绑定的 class 引用不是这种情况:当你使用 Base::class
时,你肯定知道它是 [=13= 的 class 标记] 而不是它的一些子类型,所以它是不变的 KClass<Base>
.