在接口类型的变量上访问实现的 属性
Access Implementation's property on variable of type Interface
我正在尝试访问 class (FooImpl
) 的 属性 (id
) 的委托。问题是,这个 class 实现了一个接口 (Foo
),而有问题的 属性 覆盖了这个接口的 属性。委托只存在于class中(不能存在于接口中)。
问题是,在 Foo
类型的变量上使用 ::
运算符总是 returns 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<*>
添加到 Foo
并将 override fun idProp() = ::id
添加到 FooImpl
和使用它访问 属性。
还有比这更好的方法吗?
我想到了这个,但我不知道是否有更好的方法。要解决的问题是 getDelegate()
必须 return 委托的实际实例,因此您需要 class 的实例才能检索委托实例。如果有一个内置的 hasDelegate
属性 那就太好了。您的 hasDelegate
版本会因未绑定的 KProperty1 的转换而崩溃,这就是我们在特定情况下必须处理的全部class 未知。
因此,要检索委托实例,我们需要按名称搜索 class 实例的成员属性,这会为我们提供一个协变 class 类型的超级 [=20= 的 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)}")
}
这里的问题是 属性 的所有者是在编译时解析的,而不是在运行时。当您执行 foo::id
时,foo
(因此 FooImpl
)成为它的绑定接收者,但所有者仍然解析为 Foo
。为了解决这个问题,我们需要将 属性“投射”给另一个所有者。不幸的是,我没有找到一种直接的方法来做到这一点。
我发现的一个解决方案是使用 foo::class
而不是 foo::id
,因为它在运行时解析 KClass
,而不是在编译时。然后我想出了与@Tenfour04 几乎完全相同的代码。
但是如果您不介意使用 public 且不受任何注解保护的 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)
,所以创建 foo
运行时类型的 KProperty
。等等。
我正在尝试访问 class (FooImpl
) 的 属性 (id
) 的委托。问题是,这个 class 实现了一个接口 (Foo
),而有问题的 属性 覆盖了这个接口的 属性。委托只存在于class中(不能存在于接口中)。
问题是,在 Foo
类型的变量上使用 ::
运算符总是 returns 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<*>
添加到 Foo
并将 override fun idProp() = ::id
添加到 FooImpl
和使用它访问 属性。
还有比这更好的方法吗?
我想到了这个,但我不知道是否有更好的方法。要解决的问题是 getDelegate()
必须 return 委托的实际实例,因此您需要 class 的实例才能检索委托实例。如果有一个内置的 hasDelegate
属性 那就太好了。您的 hasDelegate
版本会因未绑定的 KProperty1 的转换而崩溃,这就是我们在特定情况下必须处理的全部class 未知。
因此,要检索委托实例,我们需要按名称搜索 class 实例的成员属性,这会为我们提供一个协变 class 类型的超级 [=20= 的 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)}")
}
这里的问题是 属性 的所有者是在编译时解析的,而不是在运行时。当您执行 foo::id
时,foo
(因此 FooImpl
)成为它的绑定接收者,但所有者仍然解析为 Foo
。为了解决这个问题,我们需要将 属性“投射”给另一个所有者。不幸的是,我没有找到一种直接的方法来做到这一点。
我发现的一个解决方案是使用 foo::class
而不是 foo::id
,因为它在运行时解析 KClass
,而不是在编译时。然后我想出了与@Tenfour04 几乎完全相同的代码。
但是如果您不介意使用 public 且不受任何注解保护的 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)
,所以创建 foo
运行时类型的 KProperty
。等等。