异常与错误处理¶
Kotlin 支持异常,但与 Java 最大的差异是:Kotlin 没有受检异常。所有异常默认都是 unchecked,你可以捕获它们,但不需要在函数签名中声明,也不会被编译器强制处理。
抛出异常¶
fun requirePositive(value: Int) {
if (value <= 0) {
throw IllegalArgumentException("value 必须大于 0")
}
}
异常是对象,通常应该提供清晰的错误消息。需要保留原始原因时传入 cause:
throw IllegalStateException("配置加载失败", cause)
预条件函数¶
Kotlin 标准库提供了常用检查函数:
| 函数 | 语义 | 失败时异常 |
|---|---|---|
require() |
检查调用方传入的参数 | IllegalArgumentException |
check() |
检查对象或程序状态 | IllegalStateException |
error() |
表示不应发生的非法状态 | IllegalStateException |
require¶
fun createPage(size: Int) {
require(size in 1..100) {
"size 必须在 1..100 之间,实际是 $size"
}
}
require 适合校验函数参数。调用方传错参数时,抛 IllegalArgumentException 更符合语义。
check¶
class Session {
private var connected = false
fun send(message: String) {
check(connected) { "发送消息前必须先连接" }
println(message)
}
}
check 适合校验对象当前状态。状态不满足通常代表调用顺序错误或内部逻辑错误。
error¶
fun roleName(role: String): String =
when (role) {
"admin" -> "管理员"
"viewer" -> "访客"
else -> error("未知角色:$role")
}
error() 的返回类型是 Nothing,因此可以放在需要任意返回类型的位置。
try-catch¶
try {
val number = "42".toInt()
println(number)
} catch (e: NumberFormatException) {
println("不是合法数字")
}
多个 catch 时,应从具体异常到宽泛异常排列:
try {
process()
} catch (e: IllegalArgumentException) {
println("参数错误")
} catch (e: RuntimeException) {
println("运行时错误")
}
try 是表达式¶
val number = try {
input.toInt()
} catch (e: NumberFormatException) {
0
}
try 分支和 catch 分支的最后一个表达式会成为整体结果。finally 总会执行,但不决定 try 表达式的结果。
finally 与资源清理¶
val resource = acquireResource()
try {
resource.use()
} finally {
resource.close()
}
JVM 上处理实现 Closeable / AutoCloseable 的资源时,更推荐标准库 use:
FileWriter("output.txt").use { writer ->
writer.write("Hello")
}
这对应 Java 的 try-with-resources 思路,但 Kotlin 用库函数表达,不需要特殊语法。
自定义异常¶
class UserNotFoundException(id: Long) :
RuntimeException("找不到用户:$id")
如果希望异常可继续被继承,父异常类要显式 open:
open class DomainException(message: String, cause: Throwable? = null) :
RuntimeException(message, cause)
class OrderNotFoundException(id: Long) :
DomainException("找不到订单:$id")
不要用 object 声明异常单例。异常带有栈追踪和上下文,每次抛出都应该创建新实例。
Nothing¶
Nothing 表示永远不会正常返回的表达式或函数:
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
常见用法:
val name: String = user.name ?: fail("name required")
TODO() 也返回 Nothing:
fun calculate(): Int {
TODO("尚未实现")
}
运行到 TODO() 会抛出 NotImplementedError。不要把 TODO() 留在生产路径里。
Java 互操作¶
Java 有 checked exception,Kotlin 没有。调用 Java 方法时,即使 Java 声明了 throws IOException,Kotlin 编译器也不会强制你捕获。
如果 Kotlin 函数要被 Java 调用,并希望 Java 调用方看到 checked exception,可以使用 @Throws:
@Throws(IOException::class)
fun readConfig(): Config {
// ...
}
这样生成的 JVM 方法签名会包含 throws IOException。
安全替代函数¶
Kotlin 标准库经常提供 OrNull 版本来避免异常:
val number = input.toIntOrNull()
val first = list.firstOrNull()
val item = list.getOrNull(index)
如果“失败”是业务上可预期的分支,返回可空值或结果类型通常比抛异常更清晰。如果失败代表违反契约或不可恢复问题,异常更合适。
实践建议¶
- 参数校验用
require,状态校验用check。 - 异常消息要带业务上下文,例如 id、状态、输入值。
- 可预期失败优先使用可空值、sealed result 或领域结果类型。
- Java checked exception 迁移到 Kotlin 时,不要机械保留所有异常层级。
- 暴露给 Java 的 Kotlin API 如需 checked exception,使用
@Throws。 - 不要吞掉异常;至少保留 cause 或日志上下文。
参考¶
- 官方异常文档:https://kotlinlang.org/docs/exceptions.html