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

Kotlin反射获取可选参数默认值报对象非声明类实例错误

问题背景

通过Kotlin反射实现获取目标类中所有带默认值的可选字段、以及对应默认值的逻辑,编写代码如下:

fun getOptionalFields(componentType: KClass<out Any>): Map<String, DefaultValueData>  {
    val cons = componentType.primaryConstructor
    val constructorSetup = cons?.parameters
        ?.filterNot { it.isOptional }
        ?.associate { it to getValueForNonOptional(it) } ?: error("Something went wrong when choosing default val")
    val constructorInst = (cons.callBy(constructorSetup)::class as KClass<Any>)

    val conParams = (componentType.primaryConstructor?.parameters ?: emptyList())
        .filter { p -> p.isOptional }
        .associate { p ->
            Pair(p.name ?: "",
                DefaultValueData(
                    p.type,
// the below line crashes
                    constructorInst.memberProperties.first { m -> m.name == p.name }.get(constructorInst)
                )
            )
        }

    return conParams
}

运行代码时抛出如下异常:

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

核心疑问:对get()方法的传参规则不明确,除了待获取属性值的目标实例外,该方法还需要传入什么合法参数?

问题根因

该异常和get()方法的参数数量无关,核心bug是混淆了业务类实例对象和类的反射元数据对象:

  • KProperty1.get()是单参数方法,仅要求传入1个合法值:声明当前属性的类的真实实例,不需要传入其他额外参数。
  • 代码中val constructorInst = (cons.callBy(constructorSetup)::class as KClass<Any>)的赋值逻辑错误:cons.callBy(constructorSetup)确实返回了构造出的业务类实例,但追加::class后拿到的是该实例对应的KClass反射元数据对象,而非业务实例本身。后续从KClass元数据中取到的成员属性,都是定义在你自己的业务类上的,调用get()时传入的却是KClass元数据对象,和属性要求的接收者类型完全不匹配,因此抛出类型不匹配的异常。
修复方案

将构造出的业务实例和对应的反射元数据分开赋值,调用属性get()方法时传入真实业务实例即可,修复后代码如下:

fun getOptionalFields(componentType: KClass<out Any>): Map<String, DefaultValueData>  {
    val cons = componentType.primaryConstructor
        ?: error("Target class does not have a primary constructor")
    val constructorSetup = cons.parameters
        .filterNot { it.isOptional }
        .associate { it to getValueForNonOptional(it) }
    // 拆分:先拿到构造出的真实业务实例,再单独获取实例对应的KClass元数据
    val realInstance = cons.callBy(constructorSetup)
    val targetKClass = realInstance::class as KClass<Any>

    return cons.parameters
        .filter { it.isOptional }
        .associate { p ->
            val propValue = targetKClass.memberProperties
                .first { it.name == p.name }
                .apply { isAccessible = true } // 兼容私有属性的读取场景
                .get(realInstance) // 传入真实业务实例作为属性接收者
            p.name.orEmpty() to DefaultValueData(p.type, propValue)
        }
}
额外注意事项
  • 该方案默认类构造参数名和类成员属性名完全一致,这是Kotlin数据类、主构造函数内声明属性的默认规则,如果存在构造参数和类成员属性名不对应的场景,需要额外加匹配逻辑。
  • 如果目标类没有主构造函数、或者非可选参数无法通过getValueForNonOptional构造出合法默认值,逻辑会提前抛出错误,可以根据业务场景补充容错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 22:33:16