如何简化Kotlin中数据类可空属性到非空属性的赋值映射操作?
优化Kotlin可空数据类转非空数据类的非空校验方案
在Kotlin里处理这种可空外部类转非空内部类的场景,确实重复写requireNotNull会很繁琐,我这里有几个实用的优化方案,你可以根据自己的场景选择:
方案1:封装通用非空校验函数,减少重复代码
先写一个通用的inline函数,把属性名和错误信息的逻辑封装起来,避免每次重复写字符串模板:
inline fun <T> requireNotNullWithName(value: T?, propertyName: String): T { return requireNotNull(value) { "Missing $propertyName." } }
之后使用时就简洁很多:
val internal = Internal( first = requireNotNullWithName(external.first, "first"), second = requireNotNullWithName(external.second, "second"), third = requireNotNullWithName(external.third, "third"), fourth = requireNotNullWithName(external.fourth, "fourth") )
这个方案只是减少了重复的错误信息编写,核心逻辑还是手动映射,但胜在简单直接,没有额外复杂度。
方案2:给外部类写扩展转换函数,集中处理校验
把所有映射和校验逻辑封装到External的扩展函数里,外部调用时只需要一行代码:
fun External.toInternal(): Internal { return Internal( first = requireNotNull(first) { "Missing first." }, second = requireNotNull(second) { "Missing second." }, third = requireNotNull(third) { "Missing third." }, fourth = requireNotNull(fourth) { "Missing fourth." } ) }
调用方式超级简洁:
val internal = external.toInternal()
这个方案的优势是编译期安全,如果后续External或Internal的属性有变动,编译器会直接报错;同时所有校验逻辑集中在一个函数里,维护起来也方便,是我个人最推荐的方案,哪怕属性有20+个,集中写在一个函数里也比散在调用处清爽。
方案3:利用反射自动映射(适合属性极多的场景)
如果属性数量特别多(比如几十上百个),手动写每个属性的映射太麻烦,可以用Kotlin反射来自动完成,前提是外部类和内部类的属性名称完全一致:
inline fun <reified ExternalType : Any, reified InternalType : Any> ExternalType.mapToInternal(): InternalType { // 获取外部类的所有属性,按名称映射 val externalProps = ExternalType::class.memberProperties.associateBy { it.name } // 获取内部类的主构造函数 val internalConstructor = InternalType::class.constructors.first() // 为构造函数的每个参数找到对应的外部属性值并做非空校验 val constructorArgs = internalConstructor.parameters.map { param -> val externalProp = externalProps[param.name] ?: throw IllegalArgumentException("No matching property '${param.name}' in ${ExternalType::class.simpleName}") val value = externalProp.get(this) requireNotNull(value) { "Missing ${param.name}." } } // 调用构造函数创建Internal实例 return internalConstructor.call(*constructorArgs.toTypedArray()) }
使用时只需要指定泛型:
val internal = external.mapToInternal<External, Internal>()
⚠️ 注意:反射方案的缺点是编译期无法校验属性匹配,如果后续属性名修改不一致,会运行时报错;另外反射有一定性能开销,不适合性能敏感的高频调用场景。但对于属性极多且稳定的场景,能节省大量重复代码。
避坑提醒:不要用!!替代requireNotNull
虽然!!看起来更简洁,但它会直接抛出NullPointerException,没有自定义的错误信息,排查问题时很难快速定位是哪个属性缺失,所以强烈不推荐用!!来替代requireNotNull。
内容的提问来源于stack exchange,提问作者darth jemico
相关产品推荐
相关产品推荐

