Класс данных 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>
Другие вопросы по тегам