【问题标题】:How to change a member field with Kotlin reflection?如何使用 Kotlin 反射更改成员字段?
【发布时间】:2018-06-20 13:55:31
【问题描述】:

我正在将一个类从 Java 移植到 Kotlin。这个类声明了数百个对象。每个对象都有一个 name 属性,该属性与对象声明的变量名相同。 Java 反射允许通过反射使用声明的名称来设置对象成员name。只需在数百个构造函数中保存一个参数。

我尝试在 Kotlin 中做同样的事情,但不知道如何进行属性设置。下面是一些简化的测试代码:

import kotlin.reflect.full.companionObject
import kotlin.reflect.full.declaredMemberProperties

class MyTestObject() {

    var name: String = "NotInitialized"

    companion object {
        val Anton = MyTestObject()
        val Berta = MyTestObject()
        val Caesar = MyTestObject()
    }
}

fun main(args : Array<String>) {
    println(MyTestObject.Anton.name) // name not yet initialized

    // Initialize 'name' with the variable name of the object:
    for (member in MyTestObject::class.companionObject!!.declaredMemberProperties) {
        if (member.returnType.toString() == "myPackage.MyTestObject") {
            println("$member: ${member.name}")

            // Set 'name' property to 'member.name':
            // ???
        }
    }

    println(MyTestObject.Anton.name) // now with the initialized name
}

??? 行是我想要访问MyTestObjectname 属性以将其设置为member.name 的位置。我正在寻找类似于(member.toObject() as MyTestObject).name = member.name的函数。

【问题讨论】:

    标签: reflection kotlin


    【解决方案1】:

    虽然kotlin-reflection 努力实现类型安全,但有时类型系统和推理逻辑不足以支持您尝试以类型安全的方式执行的操作。因此,您必须进行未经检查的强制转换,说明您对类型的了解超出了编译器可以推断的范围。

    在您的情况下,只需强制转换 member 即可将伴随对象实例传递到其 .get(...) 并将结果用作 MyTestObject,将 // ??? 行替换为:

    @Suppress("UNCHECKED_CAST")
    (member as KProperty1<Any, MyTestObject>)
        .get(MyTestObject::class.companionObject!!.objectInstance!!)
        .name = member.name
    

    如果您可以将MyTestObject::class.companionObject!! 替换为MyTestObject.Companion::class(即您的实际用例不涉及从不同的类中获取.companionObject),则不需要未经检查的强制转换,您可以将上面的语句替换为:

    (member.get(MyTestObject.Companion) as MyTestObject).name = member.name
    

    作为根本不需要伴随对象反射的替代方案,您可以对委托执行相同的绑定逻辑。 Implementing provideDelegate 允许您自定义初始化属性的逻辑,这就是您可以分配名称的地方:

    operator fun MyTestObject.provideDelegate(
        thisRef: MyTestObject.Companion, 
        property: KProperty<*>
    ) = apply { name = property.name }
    
    operator fun MyTestObject.getValue(
        thisRef: MyTestObject.Companion, 
        property: KProperty<*>
    ) = this
    

    然后将你的属性声明为

    val Anton by MyTestObject()
    val Berta by MyTestObject()
    val Caesar by MyTestObject()
    

    【讨论】:

    • 你的第一个例子奏效了。我在同一类 MyTestObject 中尝试了您的第二个,但 get() 始终提供 null。没有尝试过委托示例,因为我不熟悉这种方法,而且它往往会使软件过于复杂且难以阅读。
    • 如果我将伴随对象内的代码作为初始化代码移动到属性声明之后,则第二个示例有效。这看起来是一个简单易读的解决方案。感谢您提供此解决方案!
    【解决方案2】:

    这里是基于hotkey的解决方案的最终测试代码:

    package myPackage
    
    import kotlin.reflect.full.declaredMemberProperties
    
    class MyTestObject() {
    
      lateinit var name: String
    
      companion object {
        val Anton = MyTestObject()
        val Berta = MyTestObject()
        val Caesar = MyTestObject()
    
        init {
          for (member in MyTestObject.Companion::class.declaredMemberProperties) {
            if (member.returnType.toString() == "myPackage.MyTestObject") {
              (member.get(MyTestObject.Companion) as MyTestObject).name = member.name
            }
          }
        }
      }
    }
    
    fun main(args : Array<String>) {
      println(MyTestObject.Anton.name)
      println(MyTestObject.Caesar.name)
    }
    

    【讨论】:

      猜你喜欢
      • 2018-04-21
      • 2013-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-22
      • 2014-09-17
      • 2012-02-03
      • 2011-03-19
      相关资源
      最近更新 更多