1. 概述
在本教程中,我们将深入探讨如何在 Scala 中自定义异常,以及多种处理异常的方式 —— 包括几种完全避免抛出异常的方案。
2. 自定义异常
和 Java 一样,Scala 中我们可以通过 继承 Exception
类 来创建自定义异常。
比如我们创建一个除零异常:
case class DivideByZero() extends Exception
这样就定义了一个 DivideByZero
异常。之后就可以像处理其他异常一样去 throw
或 catch
它。
当发现有人试图除以零时,我们可以抛出这个异常:
def divide(dividend: Int, divisor: Int): Int = {
if (divisor == 0) {
throw new DivideByZero
}
dividend / divisor
}
接下来我们看看 Scala 中如何优雅地处理异常。与 Java 不同,Scala 提供了多种方式,可以根据场景选择最适合的一种。
3. try/catch/finally
这是最熟悉的 异常处理 方式,和 Java 类似。
简单来说,就是将可能出错的代码放在 try
块中,处理异常的逻辑放在 catch
块中。我们来处理一下前面定义的 DivideByZero
异常:
def divideByZero(a: Int): Any = {
try {
divide(a, 0)
} catch {
case _: DivideByZero => null
}
}
这里我们捕获 DivideByZero
异常并返回 null
。
当然,也可以加上 finally
块。无论是否发生异常,finally
中的代码都会执行。
⚠️ 但注意,返回 null
在 Scala 中并不是最佳实践,容易引发空指针问题。
4. Try/Success/Failure
更 Scala 风格的异常处理方式是使用 Try/Success/Failure
。
- 如果执行成功,返回
Success
包装结果; - 如果失败,则返回
Failure
包含异常信息。
我们用这种方式来实现 divideWithTry
:
def divideWithTry(dividend: Int, divisor: Int): Try[Int] = Try(divide(dividend, divisor))
当我们调用 divideWithTry(10, 0)
时,会得到一个包含原始异常的 Failure
对象:
assert(divideWithTry(10, 0) == Failure(new DivideByZero))
调用者可以使用模式匹配来处理 Success
和 Failure
:
val result = divideWithTry(10, 0) match {
case Success(i) => i
case Failure(DivideByZero()) => None
}
✅ 这种方式避免了直接抛出异常,同时保留了错误信息,非常实用。
5. 无异常的错误处理
虽然前面我们讲了如何处理异常,但其实很多场景下我们 根本不需要抛异常。
比如“除零”这个场景,就可以完全不抛异常。而且从性能角度看,返回一个对象比生成堆栈跟踪(stack trace)要快得多。
所以,遵循“永远不要抛出你打算捕获的异常”的原则,我们来看看两种无异常的错误处理方式。
5.1. Option/Some/None
之前使用 try/catch
的方式有个缺点:返回了 null
,调用方处理起来很麻烦。
我们可以用 Option
来代替:
def divideWithOption(dividend: Int, divisor: Int): Option[Int] = {
if (divisor == 0) {
None
} else {
Some(dividend / divisor)
}
}
使用 Option
后有两点变化:
- 函数返回类型变为
Option[Int]
; - 正常情况返回
Some(value)
,出错返回None
。
调用者可以像处理 Success
和 Failure
一样,用模式匹配来处理 Some
和 None
。
⚠️ 但 Option
有个缺点:它会 丢掉原始错误信息。如果调用方需要知道具体出错原因,就不适用了。
5.2. Either/Left/Right
如果需要返回错误信息,推荐使用 Either
。
Either
表示两种互斥的结果,分别用 Left
和 Right
表示:
Left
通常表示错误;Right
表示成功的结果。
这和 Option
的区别在于,Either
可以携带错误信息。
我们用 Either
重写除法函数:
def divideWithEither(dividend: Int, divisor: Int): Either[String, Int] = {
if (divisor == 0) {
Left("Can't divide by zero")
} else {
Right(dividend / divisor)
}
}
调用者可以对 Left
和 Right
做模式匹配:
val result = divideWithEither(10, 0) match {
case Right(value) => value
case Left(error) => println(error); 0
}
✅ 这种方式兼顾了错误信息和函数式风格,是 Scala 中推荐的错误处理方式之一。
6. 总结
本文我们讲解了如何在 Scala 中定义自定义异常,并介绍了四种错误处理方式:
- 传统的
try/catch/finally
- 函数式风格的
Try/Success/Failure
- 无异常的
Option/Some/None
- 携带错误信息的
Either/Left/Right
根据不同场景选择合适的方式,可以让你的代码更健壮、更易维护。
📌 所有示例代码均可在 GitHub 仓库 中找到。