7

I'm porting a class from Java to Kotlin. This class declares hundreds of objects. Each object has a name property which is identical with the declared variable name of the object. Java reflection allows to use the declared name via reflection to set the object member name. Just saves one parameter in hundreds of constructors.

I try to do the same in Kotlin but can't figure out how to do the property setting. Here is some simplified test code:

import kotlin.reflect.full.companionObject
import kotlin.reflect.full.declaredMemberProperties

class MyTestObject() {

    var name: String = "NotInitialized"

    companion object {
        val Anton = MyTestObject()
        val Berta = MyTestObject()
        val Caesar = MyTestObject()
    }
}

fun main(args : Array<String>) {
    println(MyTestObject.Anton.name) // name not yet initialized

    // Initialize 'name' with the variable name of the object:
    for (member in MyTestObject::class.companionObject!!.declaredMemberProperties) {
        if (member.returnType.toString() == "myPackage.MyTestObject") {
            println("$member: ${member.name}")

            // Set 'name' property to 'member.name':
            // ???
        }
    }

    println(MyTestObject.Anton.name) // now with the initialized name
}

The ??? line is where I would like to get access to the name property of MyTestObject to set it to to member.name. I'm looking for a function similar to (member.toObject() as MyTestObject).name = member.name.

0

2 Answers 2

7

While kotlin-reflection strives to be type-safe, sometimes the type system and the inference logic are not enough to allow for the things like what you are trying to do in a type-safe way. So, you have to make unchecked casts, stating that your knowledge about the types is more than the compiler can infer.

In your case, it's enough to cast member so that you can pass the companion object instance into its .get(...) and use the result as a MyTestObject, replace the // ??? line with:

@Suppress("UNCHECKED_CAST")
(member as KProperty1<Any, MyTestObject>)
    .get(MyTestObject::class.companionObject!!.objectInstance!!)
    .name = member.name

If you can replace MyTestObject::class.companionObject!! with MyTestObject.Companion::class (i.e. your actual use case does not involve getting .companionObject from different classes), the unchecked cast is not needed, and you can replace the statement above with this:

(member.get(MyTestObject.Companion) as MyTestObject).name = member.name

As an alternative that does not require companion object reflection at all, you can do the same binding logic with the delegation. Implementing provideDelegate allows you to customize the logic of initializing the property, and that's where you can assign the names:

operator fun MyTestObject.provideDelegate(
    thisRef: MyTestObject.Companion, 
    property: KProperty<*>
) = apply { name = property.name }

operator fun MyTestObject.getValue(
    thisRef: MyTestObject.Companion, 
    property: KProperty<*>
) = this

Then declare your properties as

val Anton by MyTestObject()
val Berta by MyTestObject()
val Caesar by MyTestObject()
Sign up to request clarification or add additional context in comments.

2 Comments

Your first example worked. I tried your second one in the same class MyTestObject but the get() always delivers null. Have not tried the delegate example because I'm not familiar with this approach and it tends to make the software overly complex and unreadable.
The second example works if I move the code inside the companion object as init code after the property declarations. This looks like a simple and readable solution. Thank you for providing this solution!
1

Here is the final test code based on hotkey's solution:

package myPackage

import kotlin.reflect.full.declaredMemberProperties

class MyTestObject() {

  lateinit var name: String

  companion object {
    val Anton = MyTestObject()
    val Berta = MyTestObject()
    val Caesar = MyTestObject()

    init {
      for (member in MyTestObject.Companion::class.declaredMemberProperties) {
        if (member.returnType.toString() == "myPackage.MyTestObject") {
          (member.get(MyTestObject.Companion) as MyTestObject).name = member.name
        }
      }
    }
  }
}

fun main(args : Array<String>) {
  println(MyTestObject.Anton.name)
  println(MyTestObject.Caesar.name)
}

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.