KProperty1.getDelegate上的泛型限制性太强了吗?

时间:2017-04-23 10:15:42

标签: generics reflection kotlin

我正在尝试获取类层次结构中某种类型的所有属性委托。这给我提出了一些问题。 首先,似乎没有直接的方法来获得Kotlin中的类超类。我尝试的第一种方法是使用Java的Class.getSuperclass

private fun <T : Any> KClass<T>.getSuperclass(): KClass<in T>? = (java.superclass as? Class<out Any>)?.kotlin as KClass<in T>

但是这需要一个未经检查的演员阵容,因为Kotlin不会允许我将Class<in T>转换回Kotlin,因为显然在T中意味着它可能不是Any,不管是什么(“Type参数”有一个上限'任何',不能满足捕捉'投影')。

所以我尝试了一个纯粹的Kotlin解决方案:首先获取包括接口(KClass.superclasses)在内的所有超类,然后过滤掉每个接口。但KClass上没有任何内容告诉我它是否是一个界面!所以我必须再次访问Java。并且它还需要一个未经检查的强制转换,因为某些原因KClass.superclasses的类型为List<KClass<*>>而不是List<KClass<in T>>

private fun <T : Any> KClass<T>.getSuperclass123(): KClass<in T>? = superclasses.firstOrNull { it.java.isInterface } as KClass<in T>?

我在这里遗漏了什么吗?我一定是。

现在针对实际问题,尝试获取类层次结构中的所有属性委托实例。首先,我编写了一个函数来获取一个类的实例:

private fun <T : Any> KClass<T>.getProperties(obj: T) =
    declaredMemberProperties.also { it.forEach { it.isAccessible = true } }.asSequence().filter { it.getDelegate(obj) is MyPropertyDelegate }

工作正常。但是我当然还需要在超类中声明的所有属性。所以我很自然地尝试将接收器更改为KClass<in T>,这当然导致我无法再调用declaredMemberProperties。我该如何解决这个问题?

我已经尝试过使用KClass.memberProperties,但是如果子类覆盖了父类中委派的属性,那么它就会失败。在这种情况下,它只列出被覆盖的属性,这不允许我访问属性委托实例。

修改 我做了一些测试,以下Java代码编译得很好,正如我所料:

static <T> List<MyProperty> getProperties(KClass<? super T> clazz, T obj) {
    return KClasses.getDeclaredMemberProperties(clazz).stream()
            .map(p -> p.getDelegate(obj))
            .filter(MyProperty.class::isInstance)
            .map(MyProperty.class::cast)
            .collect(Collectors.toList());
}

什么是等效的Kotlin代码?

Edit²: 同样的超级东西。以下(虽然仍然是丑陋的解决方法)在Java中编译,但在Kotlin中没有编译(在这里省略空检查):

static <T> KClass<? super T> getSuperClass(KClass<T> clazz) {
    return JvmClassMappingKt.getKotlinClass(JvmClassMappingKt.getJavaClass(clazz).getSuperclass());
}

1 个答案:

答案 0 :(得分:0)

在kotlin中你的getProperties()可能会是这样的:

inline fun <reified T:Any> T.getProperties(): List<MyProperty> = T::class.declaredMemberProperties.asSequence()
        .map { it.getDelegate(this) }
        .filter(MyProperty::class::isInstance)
        .map(MyProperty::class::safeCast)
        .filterNotNull() 
        .distinct()
        .toList()

kotlin真的没有内置支持来检查KClass是否代表一个接口,这真是一件尴尬的事情。无论如何,我们有一个解决方法:

val KClass<*>.isInterface: Boolean
    get() = constructors.isEmpty()

这是任意KClass的扩展属性,测试它是否代表一个接口。这是基于以下原则:类是抽象的,开放的,内部的还是最终的,必须至少有一个构造函数,而接口根本不能有任何构造函数。但是,过滤接口并不是一个好主意,因为在kotlin中interface can also have properties

结合上述所有想法,我们得出了这样的结果:

inline fun <reified T : Any> T.getProperties(): List<MyProperty> = T::class.allSuperclasses.asSequence()
        .flatMap { it.declaredMemberProperties.asSequence() }
        .map { @Suppress("UNCHECKED_CAST") (it as KProperty1<in T, *>).getDelegate(this) }
        .filter(MyProperty::class::isInstance)
        .map(MyProperty::class::safeCast)
        .filterNotNull()
        .distinct()
        .toList()

werid unchecked cast是allClasses声明的变通方法。我没有太多地研究它的实现,所以我不知道这是一个技术限制还是累了程序员的懒惰,但那个明星投影迫使我使用一个未经检查的演员。我们都知道它会安全。