如何通过反射在Kotlin中设置伴生对象的属性?

4

当我有一个带有伴生对象的类时,是否可以使用反射在这个伴生对象中设置属性?我可以在普通属性上做到这一点,但在伴生对象上失败了:

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

调用普通属性的setter函数效果正常,但是当我调用companionProp.setter.call(companionProp, "hello")时,出现了如下异常:

Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class

请问我应该将什么作为call()的第一个参数才能成功? 编辑:我将companionProp作为第一个参数写入了代码中,但显然是错误的,实际上我已经尝试过使用companion对象,但也没有成功。
3个回答

4

对象不是声明类的实例

与Java一样,在调用反射方法时,您需要将对象本身作为第一个参数传递。

call 的第一个参数应该是伴生对象,因为这是您要修改属性的对象。

您正在传递伴生类的类对象而不是伴生对象本身。

伴生对象可以通过ClassName.Companion或在使用更进一步的反射时,通过KClass#companionObjectInstance访问。

companionProp.setter.call(WithProperty.Companion, "hello")
companionProp.setter.call(obj::class.companionObjectInstance, "hello")
companionProp.setter.call(WithProperty::class.companionObjectInstance, "hello")

运行时,两个变量都会按预期打印hello world

请注意,如果伴随对象不存在,则Foo.Companion会导致编译错误,而反射变量将返回null


我使用 obj::class.companionObjectInstance,因为在我的实际代码中,我不知道真正的类是什么。 - P.J.Meisch

3

你可以使用Java反射来解决相同的问题。

伴随类:

class Example {
    companion object {
        val EXAMPLE_VALUE = "initial"
    }
}

使用Java反射更新属性:
val field = Example::class.java.getDeclaredField("EXAMPLE_VALUE")
field.isAccessible = true
field.set(null, "replaced")

在 Android 12 上使用 Kotlin 1.5.30 进行了测试:

Log.d("test-companion", Example.EXAMPLE_VALUE) // outputs "replaced"

警告:我不确定 Java 反射在这种情况下是否可靠。它假定了 Kotlin 编译器的某些实现细节,这可能会在未来版本中更改。但该解决方案应该对于快速解决问题来说还是可以的。在我的库的下一个版本发布之前,我使用它来验证客户端的错误修复。


2

第一个参数是声明类的实例。

你传递了一个KProperty实例companionProp而不是伴生对象实例。然而,你可以使用KClass.companionObjectInstance来获取伴生实例。例如:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接