2

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)
    }
}
1
  • My usual warning to folks coming to Kotlin from more dynamic languages: in a statically-typed language like Kotlin, reflection is a code smell. It's needed for frameworks, plugins, build tools, and interoperability; but it's fragile, slow, ugly, insecure, hard to read, and turns compile-time errors into runtime ones. For general programming, there's usually a better approach. Commented Feb 16 at 23:28

1 Answer 1

2

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()
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! I used the solution you provided, although I had to correct a small error that got in - I replaced “Modifier.STATIC and Modifier.PRIVATE” with “Modifier.STATIC or Modifier.PRIVATE” (Modifier.STATIC and Modifier.PRIVATE is always equal to 0, thus always failing the condition)
@Marcoral Thanks for pointing that out! I I had apparently forgotten how bitwise operations work :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.