如何区分Kotlin函数参数是传入null还是使用默认null值?
如何区分Kotlin函数参数是未传入还是主动传入null?
有如下定义的函数:
fun foo( bar: String? = null ) { ... }
怎么判断参数bar的null是因为没传值用了默认值,还是主动传了null?也就是要区分调用是foo()还是foo(bar = null)?
实际场景问题
比如这个状态管理的例子:
data class State( val foo: String? = null, val bar: Int? = null, val baz: Long? = null, ) class StateManager { var state: State = State() private set fun setState( foo: String? = null, bar: Int? = null, baz: Long? = null, ) { state = state.copy(foo, bar, baz) } }
这段代码有问题:调用setState(bar = 1)时,会把foo和baz都覆盖成null,但直接调用state.copy(bar = 1)却不会——这是因为Kotlin data class的copy方法能区分参数是主动传入的还是用了默认值。
现有方案的局限
目前想到的一种反射调用方案:
fun setState( foo: String? = null, bar: Int? = null, baz: Long? = null, ) { val copyRef = state::copy val params = copyRef.parameters val values = mutableMapOf<KParameter, Any?>() if (foo != null) values[params[0]] = foo if (bar != null) values[params[1]] = bar if (baz != null) values[params[2]] = baz state = copyRef.callBy(values) }
它能避免把未指定的参数覆盖成null,但没法主动给某个参数赋值为null,这是个硬伤。
可行解决方案:用标记类型区分参数状态
核心思路是给每个参数加一个「状态标记」,区分「未传入」「传入null」「传入非null」三种情况。
方法1:自定义密封类(推荐)
先定义一个密封类来标记参数状态:
sealed class ParamState<out T> { // 标记参数未传入 object Unspecified : ParamState<Nothing>() // 标记参数已传入,保存传入的值(可以是null) data class Specified<out T>(val value: T?) : ParamState<T>() }
然后修改setState的参数和逻辑:
class StateManager { var state: State = State() private set fun setState( foo: ParamState<String> = ParamState.Unspecified, bar: ParamState<Int> = ParamState.Unspecified, baz: ParamState<Long> = ParamState.Unspecified, ) { state = state.copy( foo = if (foo is ParamState.Specified) foo.value else state.foo, bar = if (bar is ParamState.Specified) bar.value else state.bar, baz = if (baz is ParamState.Specified) baz.value else state.baz ) } }
调用示例:
setState():所有参数保持原状态setState(bar = ParamState.Specified(1)):只修改bar为1,foo和baz不变setState(foo = ParamState.Specified(null)):主动把foo设为null,其他参数不变
方法2:用Kotlinx的Optional
如果项目里已经引入了kotlinx-coroutines-core依赖,可以直接用Optional:
import kotlinx.coroutines.Optional class StateManager { var state: State = State() private set fun setState( foo: Optional<String?> = Optional.empty(), bar: Optional<Int?> = Optional.empty(), baz: Optional<Long?> = Optional.empty(), ) { state = state.copy( foo = foo.takeIf { it.isPresent }?.value ?: state.foo, bar = bar.takeIf { it.isPresent }?.value ?: state.bar, baz = baz.takeIf { it.isPresent }?.value ?: state.baz ) } }
调用示例:
setState():无修改setState(bar = Optional.of(1)):修改bar为1setState(foo = Optional.of(null)):主动把foo设为null
方法3:用特殊对象做默认值(不推荐)
可以用一个唯一对象作为默认值,来标记参数未传入:
private val UNSET = Any() class StateManager { var state: State = State() private set fun setState( foo: Any? = UNSET, bar: Any? = UNSET, baz: Any? = UNSET, ) { state = state.copy( foo = if (foo !== UNSET) foo as String? else state.foo, bar = if (bar !== UNSET) bar as Int? else state.bar, baz = if (baz !== UNSET) baz as Long? else state.baz ) } }
这种方式不需要额外定义类,但类型不安全,容易出现转换错误,只适合简单场景临时用。
内容的提问来源于stack exchange,提问作者Eneko
相关产品推荐
相关产品推荐

