Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change a kotlin private val using reflection?

I can access a private val value using reflection as below

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

But if I want to change info, how could I do it using reflection?

like image 723
Elye Avatar asked Dec 17 '22 15:12

Elye


1 Answers

Answer

In short, you have to use Java reflection APIs in this case, and here is how to do it:

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

Reason for using Java reflection APIs

It is not possible to do with Kotlin reflection APIs since no setter code is generated for a read-only (val) property. So to change it, we need to use Java reflection APIs which is more low-level. First, we use Tools -> Kotlin -> Show Kotlin Bytecode to see what the generated bytecode looks like. Then we see this:

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

i.e that the info fields in the MainClass Kotlin class causes the compiler to emit JVM code for a regular MainClass Java class with a final String info field. So to change it, we can use Java reflection APIs, as in the code above.

Kotlin reflection API attempt

If the field would have been private var you would be able to Use Kotlin reflection APIs like this:

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

but if you try this with private val you will get the below exception

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)

since no setter code is generated for val fields, and thus the info property will have a Kotlin Reflection API type of KProperty and not KMutableProperty.

like image 55
Enselic Avatar answered Dec 29 '22 15:12

Enselic