异常处理:try-catch-finally、Nothing类型、自定义异常、Kotlin的受检异常处理
异常处理,说白了就是给程序穿上防弹衣。你写的代码不可能永远一帆风顺,网络会断、文件会丢、用户会乱点。我做了这么多年项目,最怕的就是那种「静默失败」——程序没崩溃,但结果全错了,你还不知道错在哪。
Kotlin 的异常处理,跟 Java 比其实更简洁。但有几个关键点,你得搞明白。
try-catch-finally:基本功,但别滥用
先看最基础的写法。Kotlin 的 try-catch 可以当表达式用,这是 Java 没有的。
val result = try {
riskyOperation()
} catch (e: IOException) {
println("网络出问题了: ${e.message}")
"默认值" // catch 块最后一行就是返回值
} finally {
println("不管怎样,我都会执行")
// 注意:finally 里不要写 return,会覆盖上面的返回值
}
我个人习惯把 try-catch 用在真正不可控的地方。比如解析用户输入、读写文件、网络请求。但像空指针这种,我建议用 Kotlin 的 null 安全机制去处理,别用 try-catch 兜底。
我在项目中遇到过一个问题:有个同事在 finally 里写了 return,结果 catch 块里的返回值全被覆盖了。排查了半天才发现。嗯,这里要注意——finally 里别写 return,除非你故意想搞事情。
Nothing 类型:Kotlin 的「不可能」类型
Nothing 是 Kotlin 里一个很有意思的类型。它表示「永远不会有返回值」。你想想看,什么情况下一个函数没有返回值?
- 函数抛异常了
- 函数进入死循环了
- 函数调用了 exitProcess() 直接退出程序
Kotlin 用 Nothing 类型来标记这种情况。比如标准库里的 TODO() 函数,返回值就是 Nothing。
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
fun calculate(x: Int): Int {
if (x < 0) {
fail("x 不能为负数") // 这里返回 Nothing,编译器知道后面不会执行
}
return x * 2
}
为什么要有 Nothing?说白了,它让类型系统更严谨。编译器知道 fail() 永远不会正常返回,所以 calculate() 里 if 后面的代码,编译器能确定只有 x >= 0 时才会执行。这能帮你避免一些潜在的空指针问题。
自定义异常:别偷懒,取个好名字
Java 里自定义异常很简单,Kotlin 也一样。但我见过太多人直接抛 Exception("出错了")。你想想看,这种异常抛出来,调用方怎么处理?只能 catch (e: Exception) 一把抓,然后打印日志完事。
我建议你给异常取个有业务含义的名字。比如用户余额不足,就抛 InsufficientBalanceException;文件格式不对,就抛 InvalidFileFormatException。
class InsufficientBalanceException(
val currentBalance: Double,
val requiredAmount: Double
) : Exception("余额不足,当前 $currentBalance,需要 $requiredAmount")
fun withdraw(amount: Double) {
val balance = getBalance()
if (amount > balance) {
throw InsufficientBalanceException(balance, amount)
}
// 正常扣款逻辑
}
这样做的好处是,调用方可以精确捕获并处理。比如前端可以弹窗提示「您还差 xxx 元」,而不是显示一个笼统的「系统错误」。
Kotlin 的受检异常处理:Java 的痛点,Kotlin 的解放
Java 的受检异常(Checked Exception)是个争议很大的设计。你写个文件读取,就得 try-catch 或者 throws。代码里全是 try-catch 嵌套,看着就头疼。
Kotlin 直接取消了受检异常。所有异常都是非受检的。这意味着你不需要在函数签名上声明 throws,也不需要强制捕获。
// Java 写法
public void readFile() throws IOException {
// ...
}
// Kotlin 写法
fun readFile() {
// 不需要 throws,也不需要强制 try-catch
}
有人可能会担心:这样会不会导致异常被忽略?其实不会。Kotlin 鼓励你用更优雅的方式处理异常:
- 用 Result 类型包装可能失败的操作
- 用 runCatching 替代 try-catch
- 用 sealed class 表示成功/失败状态
// 使用 runCatching
val result = runCatching {
riskyOperation()
}
result.onSuccess { value ->
println("操作成功: $value")
}.onFailure { exception ->
println("操作失败: ${exception.message}")
}
// 或者用 Result 类型
fun fetchData(): Result<String> = runCatching {
// 网络请求
"数据"
}
我个人觉得,Kotlin 这个设计是对的。受检异常在大型项目里反而容易导致反模式——很多人直接在 catch 里写空实现,或者 throws Exception 一路往上抛。与其这样,不如让开发者自己决定怎么处理。
知识体系总览
下面这张图帮你理清 Kotlin 异常处理的核心脉络。从基础语法到高级用法,一目了然。
总结一下:Kotlin 的异常处理,核心就四个点。try-catch-finally 是基本功,Nothing 类型是 Kotlin 的特色,自定义异常体现你的设计能力,受检异常的处理方式则展示了 Kotlin 的务实态度。把这些搞明白,你写出来的代码会健壮很多。
公众号:蓝海资料掘金营,微信deep3321