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
相关产品推荐
相关产品推荐

