【问题标题】:Generics on KProperty1.getDelegate too restrictive?KProperty1.getDelegate 上的泛型过于严格?
【发布时间】:2017-04-23 10:15:42
【问题描述】:

我正在尝试获取类层次结构中某种类型的所有属性委托。这给我带来了一些问题。 首先,似乎没有直接的方法可以在 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&lt;in T&gt; 转换回 Kotlin,因为显然在 T 中意味着它可能不是 Any,无论它可能是什么(“类型参数有一个不能满足捕获'in'投影的上限'Any'”)。

所以我尝试了一个纯 Kotlin 解决方案:首先获取所有超类,包括接口 (KClass.superclasses),然后过滤掉每个接口。但是KClass 上没有任何东西告诉我它是否是一个接口!所以我必须再次去Java。它再次需要未经检查的演员表,因为出于某种原因KClass.superclasses 的类型为List&lt;KClass&lt;*&gt;&gt; 而不是List&lt;KClass&lt;in T&gt;&gt;:

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&lt;in T&gt;,这当然导致我无法再拨打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 代码是什么?

编辑²: 超一流的东西也是如此。以下(尽管仍然是丑陋的解决方法)在 Java 中编译,但在 Kotlin 中不编译(此处省略 null-check):

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

【问题讨论】:

    标签: generics reflection kotlin


    【解决方案1】:

    在 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()
    

    奇怪的未经检查的演员表是allClasses 声明的解决方法。我没有过多地研究它的实现,所以我不知道这是技术上的限制还是疲惫的程序员的懒惰,但是那个星形投影迫使我使用未经检查的演员表。我们都知道这将是安全的。

    【讨论】:

    • 首先,感谢您的回答。 isInterface 是一个不错的技巧。而且我知道接口可以具有属性,但它们不能被委托属性,所以我不需要担心它们,并且 allSuperclasses 明确指出它们没有特定的顺序。然而,我需要它们以某种定义的顺序。虽然是的,但您的回答确实提供了解决方案,但它没有回答为什么存在无法在 KClass&lt;in T&gt; 上调用 declaredMemberProperties 的限制。哦,你还没有回答如何获得实际的超类。
    猜你喜欢
    • 1970-01-01
    • 2020-08-30
    • 1970-01-01
    • 2011-02-23
    • 2017-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多