如果引用的对象有一个属性,我想检索一个属性。 (我有一个更复杂的用例,但这很好地说明了问题)。 看来,做到这一点的唯一方法是打破方差。 有安全的方法吗?
fun <T : Any> T.valueFor(name: String): Any? {
val clazz = this::class as KClass<T> // <-- unsafe to get around variance
val prop = clazz.memberProperties.firstOrNull { name == it.name }
?: throw IllegalArgumentException("No property called '$name'")
return prop.get(this) // <-- compile error here without the cast
}
这是关于该主题的精彩讨论: 没有反射的 Kotlin 属性委托
我可以通过传入所需类型的 KClass 来解决这个问题,但整个想法是编写一个事先不知道类型的通用方法。 我正在尝试考虑一个反例,这将是一件坏事,特别是考虑到我们的数据模型是扁平的(没有继承)。
@Test
fun `retrieve property`() {
open class Animal(val color: String)
class Dog(val name: String, color: String) : Animal(color)
val dog = Dog("Rocky", "Yellow")
assertThat(dog.valueFor("name")).isEqualTo(dog.name)
assertThat(dog.valueFor("color")).isEqualTo(dog.color)
val animal: Animal = dog
assertThat(animal.valueFor("name")).isEqualTo(dog.name)
assertThat(animal.valueFor("color")).isEqualTo(dog.color)
val diffAnimal = Animal("Orange")
assertThrows<IllegalArgumentException> { diffAnimal.valueFor("name") }
assertThat(diffAnimal.valueFor("color")).isEqualTo(diffAnimal.color)
}
如果您打算将此扩展用于任何接收器,则可以将
<T : Any> T.valueFor
替换为 Any.valueFor
。
正如您已经评论的那样,prop.get(this)
无法编译。这里我们需要使用kotlin.reflect.KCallable#call
重写代码
import kotlin.reflect.full.memberProperties
fun Any.valueFor(name: String): Any? { // changed to Any.valueFor
val clazz = this::class // Get the runtime type
val prop = clazz.memberProperties.firstOrNull { it.name == name }
?: throw IllegalArgumentException("No property called '$name'")
return prop.call(this) // Use `call` instead of `get`
}
代码在我的电脑上测试过。