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

如何简洁优雅地处理Kotlin中的缺失值问题

问题场景

需要优雅处理值缺失的场景(比如生成缺失值的日志提示),已编写JUnit参数化测试用例:

class MyArgumentsProvider: ArgumentsProvider {
    override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> = Stream.of(
        Arguments.of(Optional.of("an A"), Optional.of("a B"), ""),
        Arguments.of(Optional.of("an A"), Optional.empty<String>(), "no B"),
        Arguments.of(Optional.empty<String>(), Optional.of("a B"), "no A"),
        Arguments.of(Optional.empty<String>(), Optional.empty<String>(), "no A and no B"),
    )
}

@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider::class)
fun `missing values test`(maybeA: Optional<String>, maybeB: Optional<String>, expectedResult: String)

核心需求是:为每个缺失的值(如A或B)生成对应提示字符串,并用“ and ”拼接成单条消息。

发现Optional无法满足需求——空Optional无法区分是哪个值缺失。尝试用Arrow库的Either类型(用Right表示值存在,Left表示缺失提示如“no A”),通过流过滤Left实例来拼接结果:

val result = Stream.of(
    eitherOrNoA,
    eitherOrNoB
)
.flatMap { it.left().stream() }
.collect(joining(" and "))

但困惑于没有简洁惯用的方式从Optional转换为Either,想知道Kotlin原生、Arrow或其他库有没有合适的解决方案。

解决方案

1. Kotlin原生实现(无需第三方库)

Kotlin本身支持nullable类型,完全可以替代Optional直接构建缺失提示列表,不用依赖Either:

fun buildMissingMessage(a: String?, b: String?): String {
    val missing = mutableListOf<String>()
    if (a == null) missing.add("no A")
    if (b == null) missing.add("no B")
    return missing.joinToString(" and ")
}

测试时只需把Optional转成Kotlin nullable:

@Test
fun `test missing values`() {
    val cases = listOf(
        Pair(Optional.of("an A"), Optional.of("a B")) to "",
        Pair(Optional.of("an A"), Optional.empty()) to "no B",
        Pair(Optional.empty(), Optional.of("a B")) to "no A",
        Pair(Optional.empty(), Optional.empty()) to "no A and no B"
    )
    cases.forEach { (inputs, expected) ->
        val a = inputs.first.orElse(null)
        val b = inputs.second.orElse(null)
        assertEquals(expected, buildMissingMessage(a, b))
    }
}

这种写法完全贴合Kotlin惯用风格,无额外依赖,最直接高效。

2. Arrow库的简洁转换方式

如果一定要用Arrow的Either,可以给Optional扩展一个转换函数,让代码更符合惯用写法:

import arrow.core.Either
import arrow.core.left
import arrow.core.right

fun <T> Optional<T>.toEither(missingMsg: String): Either<String, T> {
    return if (isPresent) get().right() else missingMsg.left()
}

之后就能用更简洁的集合操作替代流处理:

val eitherOrNoA = maybeA.toEither("no A")
val eitherOrNoB = maybeB.toEither("no B")

val result = listOf(eitherOrNoA, eitherOrNoB)
    .mapNotNull { it.leftOrNull() } // 提取Left中的缺失提示
    .joinToString(" and ")

3. 自定义缺失类型(复杂场景扩展)

如果需要处理更多字段或更复杂的状态,可以自定义密封类来表示值的存在/缺失状态:

sealed class ValueState<T> {
    data class Present<T>(val value: T) : ValueState<T>()
    data class Missing<T>(val message: String) : ValueState<T>()
}

fun <T> Optional<T>.toValueState(missingMsg: String): ValueState<T> {
    return if (isPresent) ValueState.Present(get()) else ValueState.Missing(missingMsg)
}

构建消息时直接过滤缺失状态即可:

val stateA = maybeA.toValueState("no A")
val stateB = maybeB.toValueState("no B")

val result = listOf(stateA, stateB)
    .filterIsInstance<ValueState.Missing<*>>()
    .map { it.message }
    .joinToString(" and ")

这种方式扩展性更强,后续新增字段时只需添加对应的状态转换逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 22:20:20