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(...)
这种方式允许我们抛出任意类型的异常,而不局限于 IllegalArgumentException
或 IllegalStateException
。
示例:
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()
**,它在功能和可读性上都表现最佳,尤其适合需要链式调用和自定义异常的场景。