Как изменить приватный котлин, используя отражение? - PullRequest
2 голосов
/ 13 октября 2019

Я могу получить доступ к частному значению val, используя отражение, как показано ниже

fun main() {
    val mainClass = MainClass()
    val f = MainClass::class.memberProperties.find { it.name == "info" }
    f?.let {
        it.isAccessible = true
        val w = it.get(mainClass) as String
        println(w)
    }
}


class MainClass {
    private val info: String = "Hello"
}

Но если я хочу изменить info, как я могу сделать это с помощью отражения?

1 Ответ

2 голосов
/ 13 октября 2019

Ответ

Короче говоря, вы должны использовать API отражения Java в этом случае, и вот как это сделать:

fun main() {
    val mainClass = MainClass()
    val f = MainClass::class.java.getDeclaredField("info")
    f.isAccessible = true
    f.set(mainClass, "set from reflection")
    mainClass.printInfo() // Prints "set from reflection"
}

class MainClass {
    private val info: String = "Hello"
    fun printInfo() = println(info)
}

Причина использования API отражения Java

Невозможно работать с API отражения Kotlin, поскольку для свойства только для чтения (val) не генерируется установочный код. Таким образом, чтобы изменить его, нам нужно использовать API отражения Java, которые являются более низкоуровневыми. Сначала мы используем Инструменты -> Kotlin -> Показать байт-код Kotlin , чтобы увидеть, как выглядит сгенерированный байт-код. Затем мы видим это:

// ================MainClass.class =================
// class version 50.0 (50)
// access flags 0x31
public final class MainClass {
  // access flags 0x12
  private final Ljava/lang/String; info = "Hello"
  // ...

, то есть, что поля info в классе MainClass Kotlin заставляют компилятор генерировать код JVM для обычного MainClass Java-класса с полем final String info. Таким образом, чтобы изменить его, мы можем использовать API отражения Java, как в приведенном выше коде.

Попытка API отражения Kotlin

Если бы поле было private var, вы могли бы использовать KotlinAPI отражения:

f?.let {
    val mutableProp = it as KMutableProperty<*>
    it.isAccessible = true
    mutableProp.setter.call(mainClass, "set from Kotlin reflection")
    val w = it.get(mainClass) as String
    println(w)
}

, но если вы попробуете это с private val, вы получите следующее исключение

Exception in thread "main" java.lang.ClassCastException: class kotlin.reflect.jvm.internal.KProperty1Impl cannot be cast to class kotlin.reflect.KMutableProperty (kotlin.reflect.jvm.internal.KProperty1Impl and kotlin.reflect.KMutableProperty are in unnamed module of loader 'app')
    at MainKt.main(main.kt:107)
    at MainKt.main(main.kt)

, поскольку для полей val не генерируется установочный код,и, таким образом, свойство info будет иметь тип API отражения Kotlin KProperty, а не KMutableProperty.

...