【问题标题】:Set property by string in Kotlin using reflection?使用反射在Kotlin中按字符串设置属性?
【发布时间】:2019-07-18 23:36:57
【问题描述】:

我有一个对象:

class User {
  var id: String? = null
  var name: String? = null
}

和对列表:

val fieldsToChange = listOf<Pair<String, String>>(Pair("name", "foo"), Pair("id", "bar"))

我想迭代槽对列表并使用反射为给定属性设置适当的值。

【问题讨论】:

  • 您确定需要为此使用反射吗?您可以改用map as a delegate
  • @JuliaSamól 在我的情况下,我有包含对象字段和所需值的输入文件。对我来说,在这个特定的用例中使用反射似乎是合理的。我想知道在这种情况下是否可以使用 map 作为委托...
  • 事实上,这个“地图委托”在底层使用了反射,但我认为它比任何自定义解决方案都更具可读性。无论如何,除非出于某种原因您必须更改已经存在的 User 对象,是的,您可以以这种方式使用地图。

标签: kotlin reflection kotlin-reflect


【解决方案1】:

给定类实例 obj,我们可以使用 obj::class.memberProperties 提取名称的属性。

我们可以构造一个从属性名到属性的映射:

val nameToProperty = obj::class.memberProperties.associateBy(KProperty<*>::name)

然后我们可以遍历fieldsToChange 并检索属性并设置它:

fieldsToChange.forEach { (propertyName, propertyValue) ->
   nameToProperty[propertyName]
       .takeIf { it is KMutableProperty<*> } // take only "settable" (var) properties
       ?.let { it as KMutableProperty<*> } // cast it to mutable property so we can access setter
       ?.let { it.setter.call(obj, propertyValue) } // call the setter
}

此外,我们可以将其设为通用:

fun setFields(obj: Any, fieldsToChange: List<Pair<String, Any?>>) {
    val nameToProperty = obj::class.memberProperties.associateBy(KProperty<*>::name)
    fieldsToChange.forEach { (propertyName, propertyValue) ->
        nameToProperty[propertyName]
                .takeIf { it is KMutableProperty<*> }
                ?.let { it as KMutableProperty<*> }
                ?.let { it.setter.call(obj, propertyValue) }
    }
}

val user = User()
setFields(user, fieldsToChange)

assert(user.name == "foo")
assert(user.id == "bar")

可能的改进是优化 nameToProperty 以仅包含已转换为 KMutableProperty 的 MutableProperties

【讨论】:

  • IMO,这样做的困难只是突显了 Kotlin 是静态类型的语言。绕过所有这些,虽然可能,但很少是一个好方法:它可能性能更差,在编译时发现的错误更少,更难维护,并且需要比传统 OO 更多的代码(正如这个聪明的答案所证明的那样)。有时它是必要的,例如解析 JSON,但有一些库。
【解决方案2】:

您可以使用地图作为代表:

class User(map: Map<String, String>) {
    val id: String by map
    val name: String by map
}

val fieldsToChange = listOf(Pair("name", "foo"), Pair("id", "bar"))
val map = fieldsToChange.map { it.first to it.second }.toMap()

val user = User(map)

【讨论】:

    猜你喜欢
    • 2010-11-08
    • 1970-01-01
    • 2011-04-12
    • 2013-09-23
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    相关资源
    最近更新 更多