Skip to content

Scala 异常处理

与 Java 和其他语言类似,Scala 使用异常(exceptions)来处理中断程序正常流程的错误。方法可以通过抛出异常(throwing an exception)而不是返回值来表示错误。

Scala 没有受检异常(checked exceptions)。这意味着编译器不会强制你声明或捕获特定的异常,这提供了更大的灵活性,但也需要仔细考虑潜在的运行时错误。

抛出异常与 Java 完全相同:创建异常对象并使用 throw 关键字。

def checkAge(age: Int): Unit = {
if (age < 0) {
throw new IllegalArgumentException(s"Age cannot be negative: $age")
}
println(s"Age $age is valid.")
}
// 示例用法:
// checkAge(25) // 输出 "Age 25 is valid."
// checkAge(-5) // 抛出 IllegalArgumentException

Scala 使用 try/catch 块来处理异常。catch 块利用模式匹配来处理不同类型的异常。

import java.io.{FileNotFoundException, FileReader, IOException}
import scala.util.Using // 推荐用于资源管理
@main def CatchExceptionDemo(): Unit = {
try {
// 使用 'Using' 确保 FileReader 自动关闭
Using(new FileReader("input.txt")) { reader =>
// 处理文件内容...(此处未展示)
println("File opened successfully.")
}
} catch {
case ex: FileNotFoundException =>
println(s"Error: File not found - ${ex.getMessage}")
case ex: IOException =>
println(s"Error: I/O problem - ${ex.getMessage}")
case ex: Exception => // 最后捕获更广泛的异常
println(s"An unexpected error occurred: ${ex.getMessage}")
}
}

运行此代码(假设 input.txt 不存在):

$ scala CatchExceptionDemo
Error: File not found - input.txt (The system cannot find the file specified)

catch 块会按顺序将抛出的异常与 case 模式进行匹配。FileNotFoundException 会首先被捕获。如果发生了并非 FileNotFoundException 的 IOException,则第二个 case 会匹配。最后一个 case ex: Exception 作为其他潜在异常的“全捕获”(catch-all)。

释放文件或网络连接等资源至关重要,即使发生异常。

  1. scala.util.Using(推荐): 在 Scala 2.13 中引入,Using 提供了一种函数式且安全的方式来管理具有 close() 方法的资源。它确保无论操作成功与否或是否抛出异常,都会调用 close() 方法。(参见上面的示例)。

  2. finally 子句: 你可以使用传统的 finally 块,它始终在 try 块(以及任何运行过的 catch 块)之后执行,无论是否抛出了异常。它适用于清理代码。

import java.io.{FileNotFoundException, FileReader, IOException}
@main def FinallyDemo(): Unit = {
var reader: Option[FileReader] = None // 使用 Option 处理潜在的 null 值
try {
reader = Some(new FileReader("input.txt"))
println("File opened (using finally example).")
// 处理文件...
} catch {
case ex: FileNotFoundException =>
println("File not found (finally example).")
case ex: IOException =>
println("IO Exception (finally example).")
} finally {
println("Entering finally block...")
reader.foreach { r =>
try { r.close() } catch { case _: IOException => /* 处理关闭错误?*/ }
println("FileReader closed.")
}
}
}

输出(如果文件未找到):

$ scala FinallyDemo
File not found (finally example).
Entering finally block...

尽管 finally 有效,但通常更推荐使用 Using,因为它更简洁,并且与函数式编程风格更好地集成。

函数式错误处理(Try,Either):

Section titled “函数式错误处理(Try,Either):”

典型的 Scala 风格(Idiomatic Scala)通常更倾向于使用函数式方法处理可恢复错误,而非异常。考虑使用 scala.util.Try、scala.util.Either 或 Option 来表示可能失败的计算:

  • Option[T]: 表示一个可能存在(Some[T])或不存在(None)的值。适用于可选结果。
  • Try[T]: 表示一个计算结果,它要么是包含一个值的成功(Success[T]),要么是包含一个异常的失败(Failure[Throwable])。对于包装可能抛出异常的代码很有用。
  • Either[A, B]: 表示一个可以是两种类型之一的值,习惯上用 Left[A] 表示错误,用 Right[B] 表示成功。比 Try 更通用,因为错误类型不固定为 Throwable。
import scala.util.{Try, Success, Failure}
import scala.io.Source
def readFileContent(path: String): Try[String] = Try {
Using.resource(Source.fromFile(path))(_.mkString)
}
@main def TryDemo(): Unit = {
readFileContent("input.txt") match {
case Success(content) => println(s"File content:\n$content")
case Failure(ex) => println(s"Failed to read file: ${ex.getMessage}")
}
readFileContent("existing_file.txt") match { // 假设此文件存在
case Success(content) => println(s"Successfully read: ${content.take(20)}...")
case Failure(_) => println("This shouldn't happen if file exists")
}
}

使用 Try、Either 或 Option 允许你作为正常程序流程的一部分显式地处理潜在的失败,这通常能写出更健壮、更易于组合的代码。