How can I set property value for classes with object keyword using kotlin.reflect API? I was hoping that this is analogous to setting values in regular classes, however, this is not the case. When setting a field value, I get an exception indicating the wrong number of parameters, which even looks to me like a bug in the language itself (I would expect that if, for some reason, setting a value for this type of class was not possible, I would rather receive an OperationNotSupported-style exception).
Here is an minimal example to reproduce the problem:
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.isAccessible
fun main() {
test(RegularClass::class, RegularClass()) //This one prints "Hello world" as expected!
/* Both of these methods throw "java.lang.IllegalArgumentException: Callable expects 1 arguments, but 2 were provided"
* How to set value of kotlin "object" property? */
test(ObjectClass::class, null)
test(ObjectClass::class, ObjectClass)
}
private fun test(kClass: KClass<out Greeter>, receiver: Greeter?) {
val property = kClass.declaredMemberProperties
.filterIsInstance<KMutableProperty1<Any?, Any?>>()
.first {
it.name == "variable"
}
property.isAccessible = true
property.set(receiver, "Hello world!")
receiver?.helloWorld()
}
interface Greeter {
fun helloWorld()
}
class RegularClass: Greeter {
private var variable: String = "Wrong value"
override fun helloWorld() {
println(variable)
}
}
object ObjectClass: Greeter {
private var variable: String = "Wrong value"
override fun helloWorld() {
println(variable)
}
}
This is indeed a Kotlin bug. A private property in an object
gets compiled to a Java static field, which does not need a receiver parameter to be set. There are three different issues on YouTrack regarding this issue, for getters: KT-55449, KT-55872, and KT-23267.
A workaround for now is to detect such a field, and set it using Java reflection.
private fun test(kClass: KClass<out Greeter>, receiver: Greeter?) {
val property = kClass.declaredMemberProperties
.filterIsInstance<KMutableProperty1<Any?, Any?>>()
.first {
it.name == "variable"
}
property.isAccessible = true
val field = property.javaField
if ((field?.modifiers?.and(Modifier.STATIC or Modifier.PRIVATE) ?: 0) > 0) {
field?.set(null, "Hello World!")
} else {
property.set(receiver, "Hello world!")
}
receiver?.helloWorld()
}