Reputation: 19431
When I have a class that has a companion object, is it possible to set a property in this companion object using reflection? I can do it with normal properties, but fail on companion objects:
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.memberProperties
class WithProperty {
lateinit var prop: String
companion object {
lateinit var companionProp: String
}
fun test() = "$companionProp $prop"
}
fun main(args: Array<String>) {
val obj = WithProperty()
val prop = obj::class.memberProperties.filter { it.name == "prop" }.first()
if (prop is KMutableProperty<*>) {
prop.setter.call(obj, "world")
}
val companion = obj::class.companionObject
if (companion != null) {
val companionProp = companion.memberProperties.filter { it.name == "companionProp" }.first()
if (companionProp is KMutableProperty<*>) {
companionProp.setter.call(companionProp, "hello") // <-- what must go here as first argument?
}
}
println(obj.test())
}
Calling the setter for the normal property works as it should, but when I call companionProp.setter.call(companionProp, "hello")
I get
Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class
What do I have to pass as first argument to call()
to succeed?
Edit: I wrote companionProp
as first argument, but that definitely is wrong, I actually tried with the companion
object, but that is not working as well.
Upvotes: 4
Views: 8047
Reputation: 1261
You can solve the same problem using java reflection.
Companion class:
class Example {
companion object {
val EXAMPLE_VALUE = "initial"
}
}
Update a property using java reflection:
val field = Example::class.java.getDeclaredField("EXAMPLE_VALUE")
field.isAccessible = true
field.set(null, "replaced")
Tested with Kotlin 1.5.30 on Android 12:
Log.d("test-companion", Example.EXAMPLE_VALUE) // outputs "replaced"
WARNING: I'm not sure if java reflection is reliable for this case. It assumes some implementation details of Kotlin complier which could change in a future version. But the solution should be fine for a quick workaround. I used it to verify a bug fix on customer side before the next release of my library.
Upvotes: 4
Reputation: 14927
object is not an instance of declaring class
Just as in Java, you need to pass the object itself as the first parameter when calling reflective methods.
The first parameter to call
should be the companion object, as that is the object whose property you are trying to modify.
You are passing the companion's class object instead of the companion object itself.
A companion object is accessible either via ClassName.Companion
, or when using further reflection, through KClass#companionObjectInstance
.
companionProp.setter.call(WithProperty.Companion, "hello")
companionProp.setter.call(obj::class.companionObjectInstance, "hello")
companionProp.setter.call(WithProperty::class.companionObjectInstance, "hello")
When run, both variants print hello world
as intended.
Keep in mind that Foo.Companion
will result in a compile error if the companion object does not exist while the reflective variant will return null
instead.
Upvotes: 4
Reputation: 30686
the first argument is the instance of the declaring class.
you pass a KProperty
instance companionProp
rather than a companion object instance. However, you can using KClass.companionObjectInstance
to obtain the compantion instance. for example:
//a non-static property having a receiver, so it should be a KMutableProperty1 here
// v
if (companionProp is KMutableProperty1<*, *>) {
// get the companion object instance ---v
companionProp.setter.call(obj::class.companionObjectInstance, "hello")
}
Upvotes: 2