Scala 异常处理
异常处理是程序健壮性的重要保障。Scala 提供了多种异常处理机制,包括传统的 try-catch、函数式的 Try 类型,以及现代的错误处理模式。
基本异常处理
try-catch-finally
scala
import scala.util.{Try, Success, Failure}
import java.io.{FileReader, BufferedReader, IOException}
object BasicExceptionHandling {
def main(args: Array[String]): Unit = {
// 基本的 try-catch
def divide(x: Int, y: Int): Double = {
try {
x.toDouble / y
} catch {
case _: ArithmeticException =>
println("除零错误")
0.0
case e: Exception =>
println(s"其他错误: ${e.getMessage}")
0.0
}
}
println(s"10 / 2 = ${divide(10, 2)}")
println(s"10 / 0 = ${divide(10, 0)}")
// 带 finally 的异常处理
def readFileWithFinally(filename: String): String = {
var reader: BufferedReader = null
try {
reader = new BufferedReader(new FileReader(filename))
reader.readLine()
} catch {
case _: IOException =>
println(s"读取文件 $filename 失败")
""
case e: Exception =>
println(s"未知错误: ${e.getMessage}")
""
} finally {
if (reader != null) {
try {
reader.close()
} catch {
case _: IOException => println("关闭文件失败")
}
}
}
}
// 多种异常类型的处理
def parseAndProcess(input: String): Int = {
try {
val number = input.toInt
if (number < 0) throw new IllegalArgumentException("数字不能为负")
number * 2
} catch {
case _: NumberFormatException =>
println("输入不是有效数字")
0
case e: IllegalArgumentException =>
println(s"参数错误: ${e.getMessage}")
0
case e: Exception =>
println(s"未知错误: ${e.getMessage}")
0
}
}
val inputs = List("10", "-5", "abc", "20")
inputs.foreach(input => println(s"处理 '$input': ${parseAndProcess(input)}"))
}
}异常的抛出
scala
object ThrowingExceptions {
// 自定义异常
class InvalidAgeException(message: String) extends Exception(message)
class InsufficientFundsException(message: String, val balance: Double) extends Exception(message)
case class Person(name: String, age: Int) {
if (age < 0) throw new InvalidAgeException(s"年龄不能为负数: $age")
if (age > 150) throw new InvalidAgeException(s"年龄不能超过150: $age")
}
class BankAccount(private var balance: Double) {
def withdraw(amount: Double): Double = {
if (amount <= 0) {
throw new IllegalArgumentException("取款金额必须大于0")
}
if (amount > balance) {
throw new InsufficientFundsException(s"余额不足,当前余额: $balance", balance)
}
balance -= amount
balance
}
def getBalance: Double = balance
}
// 条件抛出异常
def validateEmail(email: String): String = {
if (email == null || email.trim.isEmpty) {
throw new IllegalArgumentException("邮箱不能为空")
}
if (!email.contains("@")) {
throw new IllegalArgumentException("邮箱格式无效")
}
email.toLowerCase
}
def main(args: Array[String]): Unit = {
// 测试自定义异常
try {
val person1 = Person("Alice", 25)
println(s"创建成功: $person1")
val person2 = Person("Bob", -5) // 会抛出异常
} catch {
case e: InvalidAgeException =>
println(s"年龄验证失败: ${e.getMessage}")
}
// 测试银行账户异常
val account = new BankAccount(1000.0)
try {
println(s"取款前余额: ${account.getBalance}")
account.withdraw(500.0)
println(s"取款500后余额: ${account.getBalance}")
account.withdraw(600.0) // 会抛出异常
} catch {
case e: InsufficientFundsException =>
println(s"取款失败: ${e.getMessage}")
println(s"当前余额: ${e.balance}")
case e: IllegalArgumentException =>
println(s"参数错误: ${e.getMessage}")
}
// 测试邮箱验证
val emails = List("user@example.com", "", "invalid-email", null)
emails.foreach { email =>
try {
val validEmail = validateEmail(email)
println(s"有效邮箱: $validEmail")
} catch {
case e: IllegalArgumentException =>
println(s"邮箱验证失败: ${e.getMessage}")
case e: NullPointerException =>
println("邮箱为null")
}
}
}
}函数式异常处理
Try 类型
scala
import scala.util.{Try, Success, Failure}
import scala.io.Source
import java.net.URL
object TryExceptionHandling {
// 使用 Try 包装可能失败的操作
def safeDivide(x: Double, y: Double): Try[Double] = {
Try(x / y)
}
def safeParseInt(str: String): Try[Int] = {
Try(str.toInt)
}
def safeReadFile(filename: String): Try[String] = {
Try {
val source = Source.fromFile(filename)
try {
source.mkString
} finally {
source.close()
}
}
}
def safeHttpRequest(url: String): Try[String] = {
Try {
val source = Source.fromURL(new URL(url))
try {
source.mkString
} finally {
source.close()
}
}
}
// Try 的链式操作
def processUserInput(input: String): Try[String] = {
for {
number <- safeParseInt(input)
doubled = number * 2
result <- Try(s"结果: $doubled")
} yield result
}
// 组合多个 Try 操作
def calculateAverage(numbers: List[String]): Try[Double] = {
val parsedNumbers = numbers.map(safeParseInt)
// 检查是否所有解析都成功
val allSuccess = parsedNumbers.forall(_.isSuccess)
if (allSuccess) {
val values = parsedNumbers.map(_.get)
Try(values.sum.toDouble / values.length)
} else {
Failure(new IllegalArgumentException("包含无效数字"))
}
}
def main(args: Array[String]): Unit = {
// 基本 Try 使用
val division1 = safeDivide(10.0, 2.0)
val division2 = safeDivide(10.0, 0.0)
division1 match {
case Success(result) => println(s"除法成功: $result")
case Failure(exception) => println(s"除法失败: ${exception.getMessage}")
}
division2 match {
case Success(result) => println(s"除法成功: $result")
case Failure(exception) => println(s"除法失败: ${exception.getMessage}")
}
// Try 的函数式操作
val numbers = List("10", "20", "abc", "30")
numbers.foreach { numStr =>
val result = safeParseInt(numStr)
.map(_ * 2)
.map(n => s"翻倍结果: $n")
.recover {
case _: NumberFormatException => s"'$numStr' 不是有效数字"
}
println(result.getOrElse("处理失败"))
}
// 链式操作
val inputs = List("5", "abc", "10")
inputs.foreach { input =>
processUserInput(input) match {
case Success(result) => println(result)
case Failure(exception) => println(s"处理失败: ${exception.getMessage}")
}
}
// 计算平均值
val numberLists = List(
List("1", "2", "3", "4", "5"),
List("10", "20", "abc"),
List("100", "200", "300")
)
numberLists.foreach { numbers =>
calculateAverage(numbers) match {
case Success(avg) => println(s"平均值: $avg")
case Failure(exception) => println(s"计算失败: ${exception.getMessage}")
}
}
}
}Option 与异常处理
scala
object OptionExceptionHandling {
// 将异常转换为 Option
def safeGet[T](list: List[T], index: Int): Option[T] = {
try {
Some(list(index))
} catch {
case _: IndexOutOfBoundsException => None
}
}
def safeDivideOption(x: Double, y: Double): Option[Double] = {
if (y == 0) None else Some(x / y)
}
def safeParseIntOption(str: String): Option[Int] = {
try {
Some(str.toInt)
} catch {
case _: NumberFormatException => None
}
}
// 使用 Option 进行安全的链式操作
def processChain(input: String): Option[String] = {
for {
number <- safeParseIntOption(input)
doubled <- Some(number * 2)
result <- if (doubled > 0) Some(s"正数结果: $doubled") else None
} yield result
}
// 组合多个 Option 操作
def combineOptions(opt1: Option[Int], opt2: Option[Int]): Option[Int] = {
for {
a <- opt1
b <- opt2
} yield a + b
}
// 从 Try 转换为 Option
def tryToOption[T](t: Try[T]): Option[T] = t.toOption
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
// 安全访问列表元素
println(s"索引 2: ${safeGet(list, 2)}")
println(s"索引 10: ${safeGet(list, 10)}")
// 安全除法
println(s"10 / 2: ${safeDivideOption(10, 2)}")
println(s"10 / 0: ${safeDivideOption(10, 0)}")
// 链式操作
val inputs = List("5", "-3", "abc", "10")
inputs.foreach { input =>
processChain(input) match {
case Some(result) => println(result)
case None => println(s"处理 '$input' 失败")
}
}
// 组合操作
val combinations = List(
(Some(1), Some(2)),
(Some(3), None),
(None, Some(4)),
(Some(5), Some(6))
)
combinations.foreach { case (opt1, opt2) =>
combineOptions(opt1, opt2) match {
case Some(sum) => println(s"$opt1 + $opt2 = $sum")
case None => println(s"无法计算 $opt1 + $opt2")
}
}
// Try 转 Option
val tryResults = List(
Try("hello".toInt),
Try("123".toInt),
Try(10 / 0)
)
tryResults.foreach { t =>
val opt = tryToOption(t)
println(s"Try 转 Option: $opt")
}
}
}
```##
Either 类型错误处理
### Either 基础使用
```scala
object EitherErrorHandling {
// 使用 Either 进行错误处理
def safeDivideEither(x: Double, y: Double): Either[String, Double] = {
if (y == 0) Left("除数不能为零")
else Right(x / y)
}
def validateAge(age: Int): Either[String, Int] = {
if (age < 0) Left("年龄不能为负数")
else if (age > 150) Left("年龄不能超过150")
else Right(age)
}
def validateEmail(email: String): Either[String, String] = {
if (email == null || email.trim.isEmpty) Left("邮箱不能为空")
else if (!email.contains("@")) Left("邮箱格式无效")
else Right(email.toLowerCase)
}
// 组合多个验证
case class User(name: String, age: Int, email: String)
def createUser(name: String, age: Int, email: String): Either[List[String], User] = {
val nameValidation = if (name.trim.nonEmpty) Right(name) else Left("姓名不能为空")
val ageValidation = validateAge(age)
val emailValidation = validateEmail(email)
(nameValidation, ageValidation, emailValidation) match {
case (Right(n), Right(a), Right(e)) => Right(User(n, a, e))
case _ =>
val errors = List(nameValidation, ageValidation, emailValidation)
.collect { case Left(error) => error }
Left(errors)
}
}
// Either 的函数式操作
def processNumber(input: String): Either[String, Int] = {
try {
val number = input.toInt
if (number > 0) Right(number * 2)
else Left("数字必须为正数")
} catch {
case _: NumberFormatException => Left(s"'$input' 不是有效数字")
}
}
// 链式操作
def calculateSquareRoot(input: String): Either[String, Double] = {
for {
number <- processNumber(input).map(_.toDouble)
sqrt <- if (number >= 0) Right(math.sqrt(number)) else Left("不能计算负数的平方根")
} yield sqrt
}
def main(args: Array[String]): Unit = {
// 基本 Either 使用
val divisions = List((10.0, 2.0), (10.0, 0.0), (15.0, 3.0))
divisions.foreach { case (x, y) =>
safeDivideEither(x, y) match {
case Right(result) => println(s"$x / $y = $result")
case Left(error) => println(s"除法错误: $error")
}
}
// 用户创建验证
val userInputs = List(
("Alice", 25, "alice@example.com"),
("", 30, "bob@example.com"),
("Charlie", -5, "invalid-email"),
("Diana", 200, "")
)
userInputs.foreach { case (name, age, email) =>
createUser(name, age, email) match {
case Right(user) => println(s"用户创建成功: $user")
case Left(errors) => println(s"用户创建失败: ${errors.mkString(", ")}")
}
}
// 链式操作
val inputs = List("4", "16", "-9", "abc", "0")
inputs.foreach { input =>
calculateSquareRoot(input) match {
case Right(result) => println(s"sqrt($input) = $result")
case Left(error) => println(s"计算失败: $error")
}
}
// Either 的 map 和 flatMap
val numberProcessing = processNumber("5")
.map(_ + 10)
.map(n => s"最终结果: $n")
println(s"数字处理结果: $numberProcessing")
}
}资源管理
自动资源管理
scala
import scala.util.{Try, Using}
import java.io.{FileWriter, BufferedWriter, FileReader, BufferedReader}
object ResourceManagement {
// 传统的资源管理(容易出错)
def writeFileTraditional(filename: String, content: String): Try[Unit] = {
Try {
var writer: BufferedWriter = null
try {
writer = new BufferedWriter(new FileWriter(filename))
writer.write(content)
} finally {
if (writer != null) {
writer.close()
}
}
}
}
// 使用 Using 进行自动资源管理(Scala 2.13+)
def writeFileUsing(filename: String, content: String): Try[Unit] = {
Using(new BufferedWriter(new FileWriter(filename))) { writer =>
writer.write(content)
}
}
def readFileUsing(filename: String): Try[String] = {
Using(new BufferedReader(new FileReader(filename))) { reader =>
Iterator.continually(reader.readLine())
.takeWhile(_ != null)
.mkString("\n")
}
}
// 多个资源的管理
def copyFile(source: String, target: String): Try[Unit] = {
Using.Manager { use =>
val reader = use(new BufferedReader(new FileReader(source)))
val writer = use(new BufferedWriter(new FileWriter(target)))
Iterator.continually(reader.readLine())
.takeWhile(_ != null)
.foreach(line => writer.write(line + "\n"))
}
}
// 自定义资源管理
class DatabaseConnection {
println("数据库连接已建立")
def query(sql: String): String = {
s"执行查询: $sql"
}
def close(): Unit = {
println("数据库连接已关闭")
}
}
// 为自定义资源实现 AutoCloseable
class ManagedDatabaseConnection extends DatabaseConnection with AutoCloseable
def withDatabase[T](operation: DatabaseConnection => T): Try[T] = {
Using(new ManagedDatabaseConnection())(operation)
}
// 手动资源管理模式
def withResource[R <: AutoCloseable, T](resource: => R)(operation: R => T): Try[T] = {
Try {
val r = resource
try {
operation(r)
} finally {
r.close()
}
}
}
def main(args: Array[String]): Unit = {
val testFile = "test.txt"
val copyFile = "copy.txt"
val content = "Hello, World!\nThis is a test file.\nScala resource management."
// 写入文件
writeFileUsing(testFile, content) match {
case scala.util.Success(_) => println("文件写入成功")
case scala.util.Failure(exception) => println(s"文件写入失败: ${exception.getMessage}")
}
// 读取文件
readFileUsing(testFile) match {
case scala.util.Success(fileContent) =>
println("文件内容:")
println(fileContent)
case scala.util.Failure(exception) =>
println(s"文件读取失败: ${exception.getMessage}")
}
// 复制文件
copyFile(testFile, copyFile) match {
case scala.util.Success(_) => println("文件复制成功")
case scala.util.Failure(exception) => println(s"文件复制失败: ${exception.getMessage}")
}
// 数据库操作
withDatabase { db =>
val result1 = db.query("SELECT * FROM users")
val result2 = db.query("SELECT * FROM orders")
List(result1, result2)
} match {
case scala.util.Success(results) =>
println("数据库查询结果:")
results.foreach(println)
case scala.util.Failure(exception) =>
println(s"数据库操作失败: ${exception.getMessage}")
}
}
}异常处理模式
错误累积模式
scala
object ErrorAccumulation {
// 验证结果类型
sealed trait ValidationResult[+A]
case class Valid[A](value: A) extends ValidationResult[A]
case class Invalid(errors: List[String]) extends ValidationResult[Nothing]
// 验证器类型
type Validator[A] = A => ValidationResult[A]
// 组合验证器
implicit class ValidationOps[A](validation: ValidationResult[A]) {
def combine[B, C](other: ValidationResult[B])(f: (A, B) => C): ValidationResult[C] = {
(validation, other) match {
case (Valid(a), Valid(b)) => Valid(f(a, b))
case (Invalid(errors1), Invalid(errors2)) => Invalid(errors1 ++ errors2)
case (Invalid(errors), _) => Invalid(errors)
case (_, Invalid(errors)) => Invalid(errors)
}
}
def map[B](f: A => B): ValidationResult[B] = validation match {
case Valid(value) => Valid(f(value))
case Invalid(errors) => Invalid(errors)
}
def flatMap[B](f: A => ValidationResult[B]): ValidationResult[B] = validation match {
case Valid(value) => f(value)
case Invalid(errors) => Invalid(errors)
}
}
// 具体验证器
def validateName(name: String): ValidationResult[String] = {
if (name.trim.isEmpty) Invalid(List("姓名不能为空"))
else if (name.length < 2) Invalid(List("姓名至少2个字符"))
else Valid(name.trim)
}
def validateAge(age: Int): ValidationResult[Int] = {
val errors = List(
if (age < 0) Some("年龄不能为负数") else None,
if (age > 150) Some("年龄不能超过150") else None
).flatten
if (errors.nonEmpty) Invalid(errors) else Valid(age)
}
def validateEmail(email: String): ValidationResult[String] = {
val errors = List(
if (email.trim.isEmpty) Some("邮箱不能为空") else None,
if (!email.contains("@")) Some("邮箱必须包含@符号") else None,
if (!email.contains(".")) Some("邮箱必须包含域名") else None
).flatten
if (errors.nonEmpty) Invalid(errors) else Valid(email.toLowerCase)
}
case class Person(name: String, age: Int, email: String)
def validatePerson(name: String, age: Int, email: String): ValidationResult[Person] = {
val nameResult = validateName(name)
val ageResult = validateAge(age)
val emailResult = validateEmail(email)
// 累积所有错误
(nameResult, ageResult, emailResult) match {
case (Valid(n), Valid(a), Valid(e)) => Valid(Person(n, a, e))
case _ =>
val allErrors = List(nameResult, ageResult, emailResult)
.collect { case Invalid(errors) => errors }
.flatten
Invalid(allErrors)
}
}
def main(args: Array[String]): Unit = {
val testCases = List(
("Alice", 25, "alice@example.com"),
("", -5, "invalid-email"),
("B", 200, ""),
("Charlie", 30, "charlie@test.com")
)
testCases.foreach { case (name, age, email) =>
validatePerson(name, age, email) match {
case Valid(person) =>
println(s"✓ 验证成功: $person")
case Invalid(errors) =>
println(s"✗ 验证失败:")
errors.foreach(error => println(s" - $error"))
}
println()
}
}
}重试模式
scala
import scala.util.{Try, Success, Failure}
import scala.concurrent.duration._
object RetryPattern {
// 简单重试
def retry[T](operation: => T, maxAttempts: Int): Try[T] = {
def attempt(attemptsLeft: Int): Try[T] = {
Try(operation) match {
case success @ Success(_) => success
case Failure(exception) if attemptsLeft > 1 =>
println(s"操作失败,剩余重试次数: ${attemptsLeft - 1}")
Thread.sleep(100) // 简单延迟
attempt(attemptsLeft - 1)
case failure => failure
}
}
attempt(maxAttempts)
}
// 带指数退避的重试
def retryWithBackoff[T](
operation: => T,
maxAttempts: Int,
initialDelay: Duration = 100.millis,
backoffFactor: Double = 2.0
): Try[T] = {
def attempt(attemptsLeft: Int, delay: Duration): Try[T] = {
Try(operation) match {
case success @ Success(_) => success
case Failure(exception) if attemptsLeft > 1 =>
println(s"操作失败: ${exception.getMessage}")
println(s"等待 ${delay.toMillis}ms 后重试,剩余次数: ${attemptsLeft - 1}")
Thread.sleep(delay.toMillis)
attempt(attemptsLeft - 1, Duration.fromNanos((delay.toNanos * backoffFactor).toLong))
case failure => failure
}
}
attempt(maxAttempts, initialDelay)
}
// 条件重试
def retryIf[T](
operation: => T,
maxAttempts: Int,
shouldRetry: Throwable => Boolean
): Try[T] = {
def attempt(attemptsLeft: Int): Try[T] = {
Try(operation) match {
case success @ Success(_) => success
case Failure(exception) if attemptsLeft > 1 && shouldRetry(exception) =>
println(s"可重试的错误: ${exception.getMessage}")
Thread.sleep(100)
attempt(attemptsLeft - 1)
case failure => failure
}
}
attempt(maxAttempts)
}
// 模拟不稳定的操作
class UnstableService {
private var callCount = 0
def unreliableOperation(): String = {
callCount += 1
if (callCount < 3) {
throw new RuntimeException(s"服务暂时不可用 (调用次数: $callCount)")
}
s"操作成功 (调用次数: $callCount)"
}
def networkOperation(): String = {
if (scala.util.Random.nextDouble() < 0.7) {
throw new java.net.ConnectException("网络连接失败")
}
"网络请求成功"
}
def databaseOperation(): String = {
if (scala.util.Random.nextDouble() < 0.5) {
throw new java.sql.SQLException("数据库连接超时")
}
"数据库操作成功"
}
}
def main(args: Array[String]): Unit = {
val service = new UnstableService()
// 简单重试
println("=== 简单重试 ===")
retry(service.unreliableOperation(), 5) match {
case Success(result) => println(s"成功: $result")
case Failure(exception) => println(s"最终失败: ${exception.getMessage}")
}
// 带退避的重试
println("\n=== 指数退避重试 ===")
retryWithBackoff(service.networkOperation(), 3) match {
case Success(result) => println(s"成功: $result")
case Failure(exception) => println(s"最终失败: ${exception.getMessage}")
}
// 条件重试
println("\n=== 条件重试 ===")
val shouldRetryNetwork = (ex: Throwable) => ex.isInstanceOf[java.net.ConnectException]
retryIf(service.databaseOperation(), 3, shouldRetryNetwork) match {
case Success(result) => println(s"成功: $result")
case Failure(exception) => println(s"最终失败: ${exception.getMessage}")
}
}
}最佳实践
异常处理指南
scala
object ExceptionBestPractices {
// 1. 使用具体的异常类型
class UserNotFoundException(userId: String) extends Exception(s"用户未找到: $userId")
class InvalidPasswordException(message: String) extends Exception(message)
// 2. 提供有意义的错误信息
def authenticateUser(username: String, password: String): Either[String, String] = {
if (username.isEmpty) {
Left("用户名不能为空")
} else if (password.length < 8) {
Left("密码长度至少8位")
} else if (!password.exists(_.isDigit)) {
Left("密码必须包含数字")
} else {
Right(s"用户 $username 认证成功")
}
}
// 3. 使用 Try 处理可能失败的操作
def safeOperation[T](operation: => T): Try[T] = Try(operation)
// 4. 优雅的错误恢复
def withFallback[T](primary: => T, fallback: => T): T = {
try {
primary
} catch {
case _: Exception => fallback
}
}
// 5. 记录异常信息
def logAndHandle[T](operation: => T, operationName: String): Option[T] = {
try {
Some(operation)
} catch {
case ex: Exception =>
println(s"操作 '$operationName' 失败: ${ex.getMessage}")
// 在实际应用中,这里应该使用日志框架
None
}
}
// 6. 避免吞噬异常
def processWithLogging[T](items: List[T])(processor: T => Unit): List[T] = {
val failed = scala.collection.mutable.ListBuffer[T]()
items.foreach { item =>
try {
processor(item)
} catch {
case ex: Exception =>
println(s"处理项目失败: $item, 错误: ${ex.getMessage}")
failed += item
}
}
failed.toList
}
def main(args: Array[String]): Unit = {
// 演示最佳实践
// 1. 具体异常类型的使用
val authResults = List(
("alice", "password123"),
("", "short"),
("bob", "validpassword1")
)
authResults.foreach { case (username, password) =>
authenticateUser(username, password) match {
case Right(message) => println(s"✓ $message")
case Left(error) => println(s"✗ 认证失败: $error")
}
}
// 2. 使用 fallback
val primaryValue = withFallback(
throw new RuntimeException("主要操作失败"),
"备用值"
)
println(s"Fallback 结果: $primaryValue")
// 3. 记录和处理
val result = logAndHandle(
10 / 0,
"除法运算"
)
println(s"记录处理结果: $result")
// 4. 批量处理错误
val numbers = List("1", "2", "abc", "4", "def")
val failedItems = processWithLogging(numbers) { numStr =>
val num = numStr.toInt
println(s"处理数字: $num")
}
println(s"处理失败的项目: $failedItems")
}
}总结
Scala 提供了多种异常处理机制:
传统异常处理:
- try-catch-finally
- 抛出自定义异常
- 适合与Java互操作
函数式错误处理:
- Try 类型:包装可能失败的操作
- Option 类型:处理可能为空的值
- Either 类型:明确的错误信息
资源管理:
- Using 类型:自动资源管理
- 自定义资源管理模式
- 确保资源正确释放
高级模式:
- 错误累积:收集所有验证错误
- 重试模式:处理临时性失败
- 优雅降级:提供备用方案
选择合适的异常处理方式取决于具体场景,函数式方法通常更安全和可组合。