如何使用Kotlin反射更改成员字段?

7

我正在将一个Java类移植到Kotlin。这个类声明了数百个对象,每个对象都有一个名称属性,该属性与对象的声明变量名称相同。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的函数。

2个回答

7
虽然 kotlin-reflection 力求类型安全,但有时类型系统和推断逻辑不足以让您以类型安全的方式完成您想要的事情。因此,您必须进行未经检查的转换,声明您对类型的了解超过编译器可以推断的程度。
在您的情况下,只需将 member 转换为可以将 companion 对象实例传递到其 .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

作为一种完全不需要伴生对象反射的替代方案,您可以使用委托执行相同的绑定逻辑。通过实现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。我还没有尝试委托示例,因为我不熟悉这种方法,而且它往往会使软件过于复杂和难以阅读。 - Tina Hildebrandt
第二个例子可以工作,如果我将代码移动到伴生对象中作为属性声明后的初始化代码。这看起来是一个简单而易读的解决方案。感谢您提供这个解决方案! - Tina Hildebrandt

1

这是基于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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接