Класс данных Kotlin: как прочитать значение свойства, если я не знаю его имени во время компиляции?
Как я могу прочитать значение свойства в экземпляре класса данных Kotlin, если имя свойства известно только во время выполнения?
2 ответа
Вот функция для чтения свойства из экземпляра класса с заданным именем свойства (выдает исключение, если свойство не найдено, но вы можете изменить это поведение):
fun <R: Any?> readPropery(instance: Any, propertyName: String): R {
val clazz = instance.javaClass.kotlin
@Suppress("UNCHECKED_CAST")
return clazz.declaredMemberProperties.first { it.name == propertyName }.get(instance) as R
}
пример использования:
// some data class
data class MyData(val name: String, val age: Int)
// and reading property "name" from an instance called "data"
val name: String = readPropery(data, "name")
Примечание: требуется зависимость kotlin-рефлекса
Вы можете сделать это с помощью рефлексии, и все это одинаково для классов данных и обычных.
Первый вариант - просто использовать отражение Java:
val name = obj.javaClass
.getMethod("getName") // to get property called `name`
.invoke(obj)
Вы даже можете сделать функцию расширения:
inline fun <reified T : Any> Any.getThroughReflection(propertyName: String): T? {
val getterName = "get" + propertyName.capitalize()
return try {
javaClass.getMethod(getterName).invoke(this) as? T
} catch (e: NoSuchMethodException) {
null
}
}
Это вызывает общественный добытчик. Чтобы получить значение частного свойства, вы можете изменить этот код, используя getDeclaredMethod
а также setAccessible
, Это также будет работать для объектов Java с соответствующими получателями (но это не соответствует соглашению is
а также has
добытчики для boolean
).
И использование:
data class Person(val name: String, val employed: Boolean)
val p = Person("Jane", true)
val name = p.getThroughReflection<String>("name")
val employed = p.getThroughReflection<Boolean>("employed")
println("$name - $employed") // Jane - true
Второй вариант предполагает использование
kotlin-reflect
Библиотека, которую вы должны добавить в свой проект отдельно, вот ее документация. Это позволит вам получить фактическое значение свойства Kotlin, игнорируя получатели Java.Ты можешь использовать javaClass.kotlin
чтобы получить токен класса Kotlin, а затем вы получите свойство от него:
val name = p.javaClass.kotlin.memberProperties.first { it.name == "name" }.get(p)
Это решение будет работать только для классов Kotlin, но не для Java, но оно гораздо надежнее, если вам нужно работать с классами Kotlin: оно не зависит от базовой реализации.
Приведенные выше ответы не помогли мне, поэтому я создал для этого функцию расширения:
@Throws(IllegalAccessException::class, ClassCastException::class)
inline fun <reified T> Any.getField(fieldName: String): T? {
this::class.memberProperties.forEach { kCallable ->
if (fieldName == kCallable.name) {
return kCallable.getter.call(this) as T?
}
}
return null
}
Это пример вызова:
val valueNeeded: String? = yourObject.getField<String>("exampleFieldName")
Также включите это в build.gradle вашего приложения:
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
Интересно, можно ли программно определить тип поля. Вы можете легко получить тип с помощью:
kCallable.returnType
Однако вам все равно нужно явно назначить общий тип:
getField<String>
вместо того
getField<kCallable.returnType>