14

Let's say I need to set property A given by a String in Kotlin object O given by a String by reflection. If O was a class I could do something like this (disregard it has no sense):

fun setValue(ownerClassName: String, fieldName: String, value : Any) {
    val enclosingClass = Class.forName(ownerClassName).newInstance()
    val enclosingClassField = enclosingClass.javaClass.getDeclaredField(fieldName)
    enclosingClassField.isAccessible = true
    enclosingClassField.set(enclosingClass, value)
}

But how would I do it if O is an object?

4
  • o.javaClass?. Commented Dec 6, 2017 at 13:13
  • To clarify, are you asking how to do this when you're passing in an existing object instance (say o: Any) rather than ownerClassName: String? Commented Dec 6, 2017 at 13:20
  • Is this Downwoting Week or something on StackOverflow? Commented Dec 6, 2017 at 14:52
  • 2
    Every week is downvoting week on SO ;) Commented Dec 6, 2017 at 15:05

3 Answers 3

27

KClass has an objectInstance field:

Class.forName(ownerClassName).kotlin.objectInstance

This is built into Kotlin reflection.

Returns: The instance of the object declaration, or null if this class is not an object declaration.

This would be even nicer if KClass had a forName method, but sadly it does not (yet), so we need to instead get the (Java) Class and convert it to KClass.

You can get a KClass instance from a Class by using the .kotlin extension property.

Then you can continue with the rest of your code. I converted this to Kotlin's reflection library:

val kClass = Class.forName(ownerClassName).kotlin
// Get the object OR a new instance if it doesn't exist
val instance = kClass.objectInstance ?: kClass.java.newInstance()

val member = kClass.memberProperties
// Has to be a mutable property, otherwise we can't set it
        .filterIsInstance<KMutableProperty<*>>()
// Check the name
        .filter { it.name == fieldName }
        .firstOrNull()

// Set the property
member?.setter?.call(instance, value)

Here is a working test:

object TestObject {
    var field = 3
}

fun setValue(ownerClassName: String, fieldName: String, value: Any) {
    val kClass = Class.forName(ownerClassName).kotlin
    val instance = kClass.objectInstance ?: kClass.java.newInstance()

    val member = kClass.memberProperties.filterIsInstance<KMutableProperty<*>>()
            .firstOrNull { it.name == fieldName }

    member?.setter?.call(instance, value)
}

fun main(args: Array<String>) {
    println(TestObject.field) // 3
    setValue("some.package.TestObject", "field", 4)
    println(TestObject.field) // 4
}
Sign up to request clarification or add additional context in comments.

9 Comments

What do you do once you have this?
It seems OP is wondering what the alternative to newInstance would be for an object (singleton), since you can't make a new instance. At least that's how I interpreted it. Then you would continue setting the field by using the rest of the code in the question
Ok, I think I interpreted the question differently (hence the downvote which I now retracted) - I assumed the OP meant an arbitrary object, rather than a Kotlin singleton object.
Actualy the question was about Kotlin singleton created by "object" keyword as opposed to "class" keyword.
Well, Aleksiej was first and his answer seems straightforward. And it does what was required.
|
13

object is translated into a class with a private constructor and a static field called INSTANCE where the only instance is stored when this class is loaded, so replacing Class.forName(ownerClassName).newInstance() with

Class.forName(ownerClassName).getDeclaredField("INSTANCE").get(null)

should work.


Javadoc:

5 Comments

What is ownerClassName now?
please provide an (at least) brief explanation how this code works. Thanks
@OliverCharlesworth The name of the object.
@crgarridos Edited, but really looking at the Javadoc for the methods Class#getDeclaredField and for Field#get would give the explanation.
This is failing in case of pro-guard enable. because object class(ownerClassName) is obfuscated.
0

Kotlin instance can be created with below code snipes

    val clazz: KClass<*> = DataRepository::class
    val constructors: Collection<KFunction<Any>> = clazz.constructors
    for (cons in constructors) {
        cons.isAccessible = true
        repository = cons.call(
            param1,
            param2,
            param3,
        ) as DataRepository
    }

Comments

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.