12

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?

Elye
  • 53,639
  • 54
  • 212
  • 474
  • 2
    I will try to find an answer for you but I can't guarantee I will succeed.. Meanwhile it would be interesting to know why you want to do this? You probably know it is generally a bad idea to circumvent compile time checks in runtime. – Enselic Oct 13 '19 at 07:28
  • Agree, it's bad thing to do. Just to see if there's a way to do it. – Elye Oct 13 '19 at 11:40

2 Answers2

32

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.

Enselic
  • 4,434
  • 2
  • 30
  • 42
-1

This is working solution

import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties

class MySolution {

var name = ""
var email = ""
}

@Suppress("UNCHECKED_CAST")
fun main() {

//Dummy Result Set
val rs = mapOf<String,String>("name" to "My Name", "email" to "My Email")

val mySol = MySolution();
val xyzMp = MySolution::class.memberProperties;
xyzMp.forEach { mp ->
   val prop =  mp as KMutableProperty<String>
    prop.setter.call(mySol, rs[mp.name])
}

println(mySol.name)
println(mySol.email)
println("*****Enjoy*******")

output

My Name
My Email
**** *Enjoy*******
Shahid Ghafoor
  • 2,991
  • 17
  • 68
  • 123