16、异常处理:异常类、try-catch-finally、throw表达式、自定义异常
写代码这件事,说白了就是跟各种意外打交道。你永远不知道用户会输入什么,网络什么时候断,文件能不能打开。我做了这么多年开发,最深的体会就是:没有异常处理的代码,就像没系安全带的赛车——跑得快,但出事就是大事。
Kotlin 的异常处理,跟 Java 很像,但又有一些自己的脾气。咱们今天就把这块彻底捋清楚。
异常类体系:Kotlin 的「事故等级」
Kotlin 里所有异常都是 Throwable 的子类。它下面分两派:
- Exception:可恢复的异常,你得处理它
- Error:系统级问题,比如内存溢出,一般不用你管
我刚开始学的时候,总觉得 Error 也得 catch 一下。后来被前辈骂了一顿——Error 是 JVM 的事,你 catch 了也做不了什么,不如让它崩了,早点暴露问题。
核心区别:Exception 是「业务上的意外」,Error 是「系统级的灾难」。你只需要关心 Exception。
try-catch-finally:三板斧
这是最基础的异常处理结构。写法跟 Java 几乎一样,但 Kotlin 有个小优势——try 可以当作表达式用,有返回值。
fun readNumber(): Int? {
val input = "123abc"
return try {
input.toInt()
} catch (e: NumberFormatException) {
println("转换失败:${e.message}")
null // 返回 null 表示失败
} finally {
println("无论成功失败,我都会执行")
}
}
你看,try 直接返回了结果。这在 Java 里做不到,你得在外面先声明一个变量。我个人习惯用这种方式简化代码,尤其是做数据转换的时候。
小技巧:finally 里不要写 return。如果你在 finally 里 return 了,它会覆盖 try 或 catch 里的返回值。我曾经踩过这个坑,排查了半天才发现是 finally 搞的鬼。
throw 表达式:主动制造「事故」
有时候,不是你遇到了异常,而是你发现情况不对,主动抛一个。Kotlin 里 throw 也是一个表达式,它有类型 Nothing。
fun validateAge(age: Int): Int {
if (age < 0) {
throw IllegalArgumentException("年龄不能为负数")
}
return age
}
这里有个有意思的点:throw 的类型是 Nothing,意味着它永远不会正常返回。编译器知道这一点,所以你可以这样写:
val name: String = name ?: throw IllegalArgumentException("name 不能为 null")
嗯,说白了就是「要么给我一个值,要么我就崩」。这种写法在数据校验时特别爽,代码很紧凑。
注意:Nothing 是 Kotlin 特有的类型。它表示「这个函数永远不会正常结束」。除了 throw,无限循环也属于 Nothing。理解这个类型,能帮你写出更严谨的代码。
自定义异常:给你的错误起个名字
Kotlin 内置的异常够用吗?大部分时候够。但如果你在做一个复杂的业务系统,比如支付、订单处理,你肯定希望异常能表达业务含义。
自定义异常很简单,继承 Exception 就行:
class InsufficientBalanceException(
val currentBalance: Double,
val requiredAmount: Double
) : Exception("余额不足,当前余额:$currentBalance,需要:$requiredAmount")
我在项目中遇到过类似场景。用户提现时余额不够,如果只抛一个 RuntimeException,前端根本不知道具体原因。自定义异常里带上余额和所需金额,前端就能直接展示给用户看:「您还差 XX 元」。
你想想看,这种体验是不是好很多?
Kotlin 的「检查型异常」去哪了?
用过 Java 的都知道,Java 有 checked exception(检查型异常),比如 IOException,你不 catch 或者不声明 throws,编译器就报错。
Kotlin 没有这个机制。所有异常都是 unchecked。为什么?
我个人觉得这个设计很明智。Java 的 checked exception 看起来安全,实际用起来很烦人。你想想看,一个简单的文件读取,你就要写一堆 try-catch,代码变得又臭又长。而且很多 checked exception 你根本处理不了,只能往上抛,最后堆到最外层。
Kotlin 的选择是:相信开发者。你觉得需要处理就 catch,不需要就让它往上抛。简洁,务实。
一句话总结:Kotlin 没有 checked exception,所有异常都是运行时异常。这不是偷懒,而是经过深思熟虑的设计。
异常处理的「避坑指南」
讲几个我踩过的坑,你遇到了能少走弯路:
- 不要用异常控制流程。异常的开销很大,频繁抛异常会影响性能。能用条件判断解决的,就别用 try-catch。
- catch 要具体。不要写
catch (e: Exception)一把抓。你想想看,如果所有异常都吞掉了,出了 bug 你根本不知道是哪里的问题。 - finally 里别 return。前面说过了,这个坑我踩过,印象特别深。
- 自定义异常要带信息。至少把出错的上下文带进去,方便排查。
知识体系总览
下面这张图,把 Kotlin 异常处理的核心知识点串起来了。你可以把它当作一个快速参考:
总结一下
异常处理这件事,说白了就是「别让程序莫名其妙地崩掉」。Kotlin 给了你足够的工具:try-catch-finally 兜底,throw 主动出击,自定义异常表达业务语义。没有 checked exception 的束缚,写起来更自由。
但自由不代表随意。我见过太多人把 try-catch 当万能药,到处吞异常。记住:异常是意外,不是流程。能用 if-else 解决的,就别麻烦异常机制。
好了,这一章就到这里。代码写多了,你自然会形成自己的异常处理风格。关键是——别让用户看到「程序崩溃」的提示,那就合格了。