简体   繁体   中英

Kotlin: determine property name when reference is known

Suppose we have two classes A and B. The framework will require that A will have 0 or more properties that are of type B. The user can have a reference to an instance of B. For simplicity, assume that type B can only be declared within class A.

class A {

    private val myFirstB = B(this)
    private val mySecondB = B(this)

    val listOfB: List<B> = mutableListOf(myFirstB, mySecondB)
    
}

class B(a: A) {
    val myA : A = a
    fun doSomething(){
        // great stuff
    }
}

fun testIt(){
    val a = A()
    val b1 = a.listOfB[0]
}

Through Kotlin reflection how can we determine the name of the property that is holding the reference to b1 within A when we only have the reference b1. We also know that B has a reference to A. Through the instance B, we can get the instance of A. Using reflection, we can get the properties of A via the declaredMemberProperties property. This can be iterated through to get the names of all the properties. However, I do not understand how to ensure that the name that is retrieved is associated with the reference b1.

As said in comments, your case looks very specific and usually reflection isn't ideal for such cases. The algorithm either has to target this very specific case, including the fact the b1 is always inside a list. Or it would have to support many different cases and "guess" where to look for b1 .

Basically, the idea is to get the value of each member and compare it to the searched value:

fun main() {
    val a = A()
    val b1 = a.listOfB[0]

    // prints: "listOfB"
    println(findPropertyNameByValue(a, b1))
}

fun findPropertyNameByValue(owner: Any, value: Any): String {
    return owner::class.memberProperties.first { prop ->
        @Suppress("UNCHECKED_CAST")
        val list = (prop as KProperty1<Any, *>).get(owner)
        list is List<*> && list.any { it === value }
    }.name
}

We can even get "listOfB" from the b1 alone, then the algorithm would have to iterate through members twice, again by guessing if the value is our A or not. But it is technically possible.

After some experimentation, the getter call holds the reference that is needed. Here is some possible code:

import kotlin.reflect.KClass
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.KProperty1

fun main(){
    val a = A()
    val b1 = a.listOfB[0]
    println(b1)
    val clz: KClass<out A> = a::class
    val properties: Collection<KProperty1<out A, *>> = clz.declaredMemberProperties
    for(p in properties){
        println(p)
        println(p.name)
        println(p.getter.call(a))
        if (p.getter.call(a)?.equals(b1) == true){
            println("This is b1")
        }
    }
}

class A {

    val myFirstB = B(this)
    val mySecondB = B(this)

    val listOfB: List<B> = mutableListOf(myFirstB, mySecondB)

}
var cnt = 1
class B(a: A) {
    val myA : A = a
    val s = cnt++
    fun doSomething(){
        // great stuff
    }
}

This has the following output:

ksl.B@4590c9c3
val ksl.A.listOfB: kotlin.collections.List<ksl.B>
listOfB
[ksl.B@4590c9c3, ksl.B@d62fe5b]
val ksl.A.myFirstB: ksl.B
myFirstB
ksl.B@4590c9c3
This is b1
val ksl.A.mySecondB: ksl.B
mySecondB
ksl.B@d62fe5b

As can be seen, the test of if the getter reports that it correctly finds b1

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM