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

如何区分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为1
  • setState(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 12:13:10