1. 概述

在本教程中,我们将深入探讨如何在 Scala 中自定义异常,以及多种处理异常的方式 —— 包括几种完全避免抛出异常的方案。

2. 自定义异常

和 Java 一样,Scala 中我们可以通过 继承 Exception 来创建自定义异常。

比如我们创建一个除零异常:

case class DivideByZero() extends Exception

这样就定义了一个 DivideByZero 异常。之后就可以像处理其他异常一样去 throwcatch 它。

当发现有人试图除以零时,我们可以抛出这个异常:

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))

调用者可以使用模式匹配来处理 SuccessFailure

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

调用者可以像处理 SuccessFailure 一样,用模式匹配来处理 SomeNone

⚠️ 但 Option 有个缺点:它会 丢掉原始错误信息。如果调用方需要知道具体出错原因,就不适用了。

5.2. Either/Left/Right

如果需要返回错误信息,推荐使用 Either

Either 表示两种互斥的结果,分别用 LeftRight 表示:

  • 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)
  }
}

调用者可以对 LeftRight 做模式匹配:

val result = divideWithEither(10, 0) match {
  case Right(value) => value
  case Left(error) => println(error); 0
}

✅ 这种方式兼顾了错误信息和函数式风格,是 Scala 中推荐的错误处理方式之一。

6. 总结

本文我们讲解了如何在 Scala 中定义自定义异常,并介绍了四种错误处理方式:

  1. 传统的 try/catch/finally
  2. 函数式风格的 Try/Success/Failure
  3. 无异常的 Option/Some/None
  4. 携带错误信息的 Either/Left/Right

根据不同场景选择合适的方式,可以让你的代码更健壮、更易维护。

📌 所有示例代码均可在 GitHub 仓库 中找到。


原始标题:Error Handling

« 上一篇: Cake 模式