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

时间:2017-10-20 10:36:38

标签: reflection kotlin

我将一个类从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
}

???行是我希望访问name的{​​{1}}属性以将其设置为MyTestObject的位置。我正在寻找类似于member.name的功能。

2 个答案:

答案 0 :(得分:3)

虽然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()

答案 1 :(得分:0)

以下是基于热键解决方案的最终测试代码:

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)
}