为什么表达式objectOfTypeT :: class的类型是KClass <out t =“”>?

时间:2017-06-29 03:40:15

标签: kotlin

假设我们有通用功能:

fun <T: Any> foo(o: T) {
    o::class
}

o::class的类型为KClass<out T>。为什么存在out方差注释,为什么不是KClass<out Any>(因为T的删除是Any

这个out方差注释搞砸了我漂亮的反射代码

编辑: 经过一段时间的挖掘,我发现kotlin依靠Object::getClass获得Class来创建KClass,实际的创建代码有一个像fun <T: Any> create(jClass: Class<T>): KClass<T>这样的签名。然而,这导致另一个问题。 o::class应为KClass<Any> 类型,因为该创建方法的jClass参数应为Class<Object>类型,因为删除了静态类型{ {1}}只是T(或Any,映射到JVM上)。

1 个答案:

答案 0 :(得分:2)

为什么存在out方差注释?

这是kotlin 1.1中Bounded Class Reference的预期行为。

我们知道子类的实例可以分配给超类,例如:

val long:Number = 1L;
val int:Number = 1;

我们也知道泛型继承不像类继承,例如:

val long:KClass<Long> = Long::class;
val number:KClass<Number> = long;
//                           ^
// can't be compiled, required KClass<Number> but found KClass<Number>

因此我们使用Generic Type Projection编译代码,如下所示:

val number:KClass<out Number>  = long;

简而言之,supperclass(Number)的变量可以分配给任何子类的实例(LongIntDouble和.etc),但是当从KClass引用中获取Number引用时,它应该返回KClass<out Number>而不是KClass<Number>,因为KClass<Int>不是KClass<Number>的子类型1}}。

同样的规则适用于java,例如:

Number number = 1L;

Class<? extends Number> type = number.getClass();

为什么不是KClass(因为T的擦除是Any)?

因为您的方法使用通用参数T,但java.lang.Object#getClass根本不使用任何通用参数,其返回类型为Class<? extends Object>

但是,T#javaClass属性采用通用参数T,您可以看到下面的代码kotin将Class<?>投射到Class<T>。因此,o::class方法中foo的{​​{3}}为KClass<? extends T>,而不是KClass<? extends Object>。{/ p>

public inline val <T: Any> T.javaClass : Class<T>
   @Suppress("UsePropertyAccessSyntax")
   get() = (this as java.lang.Object).getClass()   as Class<T>
   // force to casting a Class<?> to a Class<T> ---^

KClass<? extends T>KClass<?>的子类型,根据Upper Bounded Wildcard,您无法将超类实例分配给子类类型。

fun <T : Any> foo(value: T) {
    val type: Class<out T> = (value as java.lang.Object).getClass();
    //   ^
    // Compilation Error:
    // you can't assign Class<? extends Object> to a Class<? extends T>
}

您还可以看到方法通用签名如下:

val noneGenericParametersMethod= Object::class.java.getDeclaredMethod("getClass")!!
val genericParametersMethod by lazy {
    val it = object {
        fun <T : Any> foo(): Class<out T> = TODO();
    };
    return@lazy it.javaClass.getDeclaredMethod("foo")!!;
}

println(genericParametersMethod.toGenericString())
//      ^--- its return type is java.lang.Class<? extends T>

println(noneGenericParametersMethod.toGenericString())
//      ^--- its return type is java.lang.Class<? extends Object>

基于上述内容,表达式o::class实际上返回LISP principle KClass而不是参数化类型KClass<out T>,并且Raw Type可以分配给KClass任何Raw Type,但是,kotlin没有Parameterized Type,因此kotlin编译器将原始类型KClass<out T>缩小为参数化类型List<*>,就像缩小Iterable<*>一样到Box rawBox = new Box(); // rawBox is a raw type of Box<T> Box<Integer> intBox = rawBox; // warning: unchecked conversion 。 java中的Raw Type示例:

getClass

为什么java无法将T.getClass()分配到Class&lt;?扩展T>?

如果您深入了解Raw Type方法的文档,您将找到如下结果:

  

实际结果类型是 Class&lt;?扩展 | X | &gt; 其中 | X | 是表达式{{1>的静态类型的擦除}} 叫做。

删除静态类型”:这意味着|X|是有界类型而不是运行时的实际通用参数类型,例如:

// Object.getClass() using the bounded static type `Number`
//         v    
<T extends Number> Class<? extends Number> foo(T value){
    return value.getClass();
}