【问题标题】:Access Implementation's property on variable of type Interface访问接口类型变量的实现属性
【发布时间】:2023-04-05 11:27:01
【问题描述】:

我正在尝试访问类 (FooImpl) 的属性 (id) 的委托。问题是,这个类实现了一个接口(@98​​7654323@),而有问题的属性覆盖了这个接口的一个属性。委托只存在于类中(不能存在于接口中)。

问题是在Foo 类型的变量上使用:: 运算符总是返回Foo 的属性,而不是实际实例的属性。代码中的问题:

import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible

interface Foo {
    val id: Int
}

class FooImpl(
    id: Int,
) : Foo {
    override val id: Int by lazy { id }
}

val <T> KProperty<T>.hasDelegate: Boolean
    get() = apply { isAccessible = true }.let { (it as KProperty0<T>).getDelegate() != null }

fun main() {
    val foo: Foo = FooImpl(1)
    println("foo::id.hasDelegate = ${foo::id.hasDelegate}")
    println("(foo as FooImpl)::id.hasDelegate = ${(foo as FooImpl)::id.hasDelegate}")
}

打印出来:

foo::id.hasDelegate = false
(foo as FooImpl)::id.hasDelegate = true

但这需要正确实现的编译时知识。我正在寻找的是访问正确的属性,而不必在那里指定FooImpl

该信息在运行时存在,因为到目前为止我发现的最少 (!) 侵入性解决方法是将 fun idProp(): KProperty0&lt;*&gt; 添加到 Foooverride fun idProp() = ::idFooImpl 并使用它访问属性。

还有比这更好的方法吗?

【问题讨论】:

    标签: kotlin reflection kotlin-reflect


    【解决方案1】:

    我想出了这个,但我不知道是否有更好的方法。要解决的问题是getDelegate() 必须返回委托的实际实例,因此您需要类的实例才能检索委托实例。如果有一个内置的hasDelegate 属性,那就太好了。您的hasDelegate 版本将在未绑定的KProperty1 的演员表中崩溃,这是我们在特定类未知时必须处理的全部内容。

    所以要检索委托实例,我们需要按名称搜索类实例的成员属性,这为我们提供了一个具有超类类型的协变类类型的 KProperty。由于它是协变的,我们可以调用像 getDelegate() 这样的消费函数,而无需转换为不变类型。我认为这在逻辑上应该是安全的,因为我们正在传递一个我们知道具有与我们检索属性的::class 匹配类型的实例。

    @Suppress("UNCHECKED_CAST")
    fun <T: Any> KProperty1<T, *>.isDelegated(instance: T): Boolean =
        (instance::class.memberProperties.first { it.name == name } as KProperty1<T, *>).run {
            isAccessible = true
            getDelegate(instance) != null
        }
    
    fun main() {
        val foo: Foo = Foo2()
        println("foo::id.hasDelegate = ${Foo::id.isDelegated(foo)}")
    }
    

    【讨论】:

      【解决方案2】:

      这里的问题是属性的所有者是在编译时解决的,而不是在运行时解决的。当您执行foo::id 时,foo(所以FooImpl)成为它的绑定接收者,但所有者仍解析为Foo。为了解决这个问题,我们需要将财产“投”给另一个所有者。不幸的是,我没有找到一个简单的方法来做到这一点。

      我找到的一个解决方案是使用foo::class 而不是foo::id,因为它在运行时而不是在编译时解析KClass。然后我想出了与@Tenfour04 几乎完全相同的代码。

      但如果您不介意使用公开且不受任何注释保护的 Kotlin 内部结构,您可以使用更简洁的解决方案:

      val KProperty0<*>.hasDelegate: Boolean
          get() = apply { isAccessible = true }.getDelegate() != null
      
      fun KProperty0<*>.castToRuntimeType(): KProperty0<*> {
          require(this is PropertyReference0)
          return PropertyReference0Impl(boundReceiver, boundReceiver::class.java, name, signature, 0)
      }
      
      fun main() {
          val foo: Foo = FooImpl(1)
          println(foo::id.castToRuntimeType().hasDelegate) // true
      }
      

      我们基本上创建了一个KProperty 的新实例,复制其所有数据,但将所有者更改为与其绑定接收者相同的类型。结果,我们将其“强制转换”为运行时类型。这更简单,也更干净,因为我们将属性转换和检查委托分开。

      不幸的是,我认为 Kotlin 反射 API 仍然缺少很多功能。应该有hasDelegate() 函数,所以我们不必提供接收者,这并不是真正需要检查属性是否被委托。应该可以将KProperty 转换为另一种类型。应该可以使用一些 API 调用来创建绑定属性。但首先,应该可以做类似的事情:Foo::id(foo),所以创建运行时类型为fooKProperty。以此类推。

      【讨论】:

      • 谢谢,这可能是我目前情况下所能期望的最好结果。为我的目的工作。有趣的是,我无法将其适应KProperty1(对于像Foo::id 这样的无接收属性),因为那里的类层次结构不同并且完全是internal
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-11
      • 2013-05-10
      • 1970-01-01
      相关资源
      最近更新 更多