1. 概述

异常处理 是软件开发中不可或缺的一部分。

在本教程中,我们将探讨在 Kotlin 中进行条件异常抛出(Conditional Exception Throwing)的一些惯用方法。Kotlin 作为一种现代且简洁的编程语言,为我们提供了多种方式来优雅地处理这类逻辑。

2. 理解条件异常抛出

所谓条件异常抛出,指的是 在满足特定条件时抛出异常 的做法:

if (condition) {
    throw SomeException(...)
}

Kotlin 提供了多种方式来更简洁地表达这种逻辑,从而提升代码的可读性并减少样板代码。我们将在下文中介绍几种常见的实现方式。

3. 使用标准函数 require() 与 check()

Kotlin 提供了几个内置的检查函数,如 require()requireNotNull()check()checkNotNull(),用于简化条件检查并抛出异常。

require()requireNotNull()

当我们需要抛出 IllegalArgumentException 时,可以使用 require()requireNotNull()

示例:

val str = "a b c"
assertThrows<IllegalArgumentException> {
    require(str.length > 10) { "The string is too short." }
}

由于 require() 返回的是 Unit,无法直接链式调用后续操作。解决办法是使用 also() 等作用域函数

val upperStr = str.also {
    require(it.split(" ").size == 3) { "Format not supported" }
}.uppercase()
assertEquals("A B C", upperStr)

requireNotNull()require() 类似,但它会在参数为 null 时抛出异常,并返回非空值,便于链式调用:

var nullableValue: String? = null
assertThrows<IllegalArgumentException> {
    requireNotNull(nullableValue) { "Null is not allowed" }
}

nullableValue = "a b c"
val uppercaseValue = requireNotNull(nullableValue).uppercase()
assertEquals("A B C", uppercaseValue)

check()checkNotNull()

这两个函数与 require() 类似,但它们抛出的是 IllegalStateException

val str = "a b c"
assertThrows<IllegalStateException> {
    check(str.length > 10) { "The string is too short." }
}

var nullableValue: String? = null
assertThrows<IllegalStateException> {
    checkNotNull(nullableValue) { "Null is not allowed" }
}

nullableValue = "a b c"
val uppercaseValue = checkNotNull(nullableValue).uppercase()
assertEquals("A B C", uppercaseValue)

4. 使用 takeIf() ?: throw ...

当需要抛出自定义异常时,可以使用 takeIf() 配合 Elvis 操作符 ?:

takeIf { condition } ?: throw SomeException(...)

这种方式允许我们抛出任意类型的异常,而不局限于 IllegalArgumentExceptionIllegalStateException

示例:

val str = "a b c"
assertThrows<MyException> {
    str.takeIf { it.length > 10 } ?: throw MyException("The string is too short.")
}

⚠️ 注意takeIf() 返回 null 的情况有两种:

  • 条件为 false
  • 接收者本身为 null

因此,null 是合法值时,不建议使用这种方式,否则会误判。

5. 自定义 throwIf() 函数

为了进一步提高代码的可读性和复用性,我们可以封装一个 throwIf() 函数:

inline fun throwIf(throwCondition: Boolean, exProvider: () -> Exception) {
    if (throwCondition) throw exProvider()
}

示例:

val str = "a b c"
assertThrows<MyException> {
    throwIf(str.length <= 10) { MyException("The string is too short.") }
}

由于 exProvider 是一个函数,只有在条件为 true 时才会执行,避免了不必要的异常对象创建

同样,我们可以结合 also() 实现链式调用:

val uppercaseValue = str.also {
    throwIf(it.split(" ").size != 3) { MyException("Format not supported") }
}.uppercase()
assertEquals("A B C", uppercaseValue)

6. 自定义 mustHave() 函数

前面介绍的方法各有优劣,我们希望设计一个更通用、更灵活的条件检查函数。

✅ 设计目标

  • 支持任意异常类型
  • 支持扩展函数调用
  • 返回接收者对象,支持链式调用
  • 异常提供者为函数,支持延迟初始化
  • 条件为函数,支持复杂判断
  • 在 lambda 中可直接引用接收者

✅ 实现 mustHave()

inline fun <T : Any?> T.mustHave(
    otherwiseThrow: (T) -> Exception = { IllegalStateException("mustHave check failed: $it") },
    require: (T) -> Boolean
): T {
    if (!require(this)) throw otherwiseThrow(this)
    return this
}

✅ 使用示例

假设我们有如下数据类和异常类:

data class Player(val id: Int, val name: String, val score: Int)
class InvalidPlayerException(message: String) : RuntimeException(message)

我们可以这样使用:

val kai = Player(1, "Kai", -5)
assertThrows<IllegalStateException> { kai.mustHave { it.score >= 0 } }
    .also { ex -> assertEquals("mustHave check failed: Player(id=1, name=Kai, score=-5)", ex.message) }

指定异常类型:

assertThrows<InvalidPlayerException> {
    kai.mustHave(
        require = { it.score >= 0 },
        otherwiseThrow = { InvalidPlayerException("Player [id=${it.id}, name=${it.name}] is invalid") }
    )
}.also { ex -> assertEquals("Player [id=1, name=Kai] is invalid", ex.message) }

链式调用示例:

val liam = Player(2, "Liam", 42)
val upperDescription = liam.mustHave(
    require = { it.score >= 0 },
    otherwiseThrow = { InvalidPlayerException("Player [id=${it.id}, name=${it.name}] is invalid") }
).let { "${it.name} : ${it.score}".uppercase() }

assertEquals("LIAM : 42", upperDescription)

7. 总结

在本教程中,我们探讨了 Kotlin 中实现条件异常抛出的多种方式,包括:

方法 支持异常类型 是否支持链式调用 是否支持自定义异常 备注
require() / check() 固定类型 否(需配合 also() 简洁,但灵活性有限
takeIf() ?: throw 不能处理 null 为合法值的情况
throwIf() 否(需配合 also() 可读性好,适合简单判断
mustHave() 最灵活,推荐用于复杂场景

完整示例代码可在 GitHub 获取。

✅ **推荐使用 mustHave()**,它在功能和可读性上都表现最佳,尤其适合需要链式调用和自定义异常的场景。


原始标题:Conditional Exception Throwing in Kotlin