You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过反射实现从父对象获取成员属性名的getMemberName函数

问题解答

Kotlin标准反射库没有直接通过值逆向获取成员路径的通用方案——因为值本身不携带任何上下文信息,同一个值可能对应多个不同的成员(比如两个属性值相同的场景),反射无法准确推导。

如果要实现可靠的getMemberName功能,推荐通过属性引用或lambda捕获访问路径的方式实现,而非传入值。以下是两种可行的实现方案:

方案1:使用属性引用(类型安全)

通过Kotlin的KProperty类型直接传递属性引用,支持嵌套属性的链式调用:

import kotlin.reflect.KProperty1

data class Inner(val prop: String)
data class Source(
    val member: String,
    val innerMember: Inner
)

// 获取单级属性名称
fun <T, R> getMemberName(property: KProperty1<T, R>): String = property.name

// 扩展方法,支持嵌套属性拼接
fun <T, R, V> KProperty1<T, R>.dot(property: KProperty1<R, V>): KProperty1<T, V> {
    return object : KProperty1<T, V> {
        override val name: String get() = "${this@dot.name}.${property.name}"
        override val returnType = property.returnType
        override val annotations = emptyList()
        override fun get(receiver: T): V = property.get(this@dot.get(receiver))
    }
}

fun main() {
    val demo = Source("asd", Inner("asd2"))
    
    val memberName = getMemberName(Source::member)
    println(memberName) // 输出: member

    val innerMemberName = getMemberName(Source::innerMember.dot(Inner::prop))
    println(innerMemberName) // 输出: innerMember.prop
}

方案2:使用Lambda捕获访问路径

通过内联Lambda捕获属性访问逻辑,解析Lambda的调用信息获取路径(注意:此方式依赖Kotlin反射的内部实现,版本变动可能影响兼容性):

import kotlin.reflect.full.valueParameters

data class Inner(val prop: String)
data class Source(
    val member: String,
    val innerMember: Inner
)

inline fun <reified T, R> getMemberName(crossinline selector: T.() -> R): String {
    val lambda = selector as (T) -> R
    val function = lambda::class.java.getDeclaredField("function").apply { isAccessible = true }.get(lambda)
    val callable = function as kotlin.reflect.KFunction<*>
    
    // 从Lambda的toString输出中提取属性路径
    return callable.toString()
        .substringAfterLast("-> ")
        .substringBefore(")")
        .replace("this.", "")
}

fun main() {
    val demo = Source("asd", Inner("asd2"))
    
    val memberName = getMemberName { it: Source -> it.member }
    println(memberName) // 输出: member

    val innerMemberName = getMemberName { it: Source -> it.innerMember.prop }
    println(innerMemberName) // 输出: innerMember.prop
}

不推荐的方案:通过值遍历匹配

如果硬要通过值来实现,只能遍历所有成员和嵌套成员进行值匹配,但这种方式不可靠且性能差——遇到重复值时会返回第一个匹配的成员,无法保证正确性:

import kotlin.reflect.full.memberProperties

fun getMemberName(source: Any, targetValue: Any): String? {
    val sourceClass = source::class
    // 遍历当前类的所有成员
    for (prop in sourceClass.memberProperties) {
        val value = prop.get(source)
        if (value == targetValue) return prop.name
        
        // 递归遍历嵌套对象的成员(跳过基本类型和字符串)
        val returnType = prop.returnType.classifier as? Class<*> ?: continue
        if (!returnType.isPrimitive && returnType != String::class.java) {
            val nestedPath = getMemberName(value, targetValue)
            if (nestedPath != null) return "${prop.name}.$nestedPath"
        }
    }
    return null
}

fun main() {
    val demo = Source("asd", Inner("asd2"))
    println(getMemberName(demo, demo.member)) // 输出: member
    println(getMemberName(demo, demo.innerMember.prop)) // 输出: innerMember.prop
    
    // 存在值重复时出错
    val demo2 = Source("asd2", Inner("asd2"))
    println(getMemberName(demo2, "asd2")) // 输出: member(而非预期的innerMember.prop)
}

内容的提问来源于stack exchange,提问作者Nikita Chegodaev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 23:15:29