在 Scala 里,异常处理语法和 Java 很接近,但一旦进入 RPC、Future 或其他异步链路,单纯依赖 try/catch 往往会让分支散落、代码变重。本文从基础捕获写起,再过渡到 scala.util.Try 的几种常见写法,帮助你判断:什么时候直接捕获异常,什么时候把失败显式包装成 Success/Failure,以及默认值和定向恢复该怎么落地。
传统写法:Scala 里的 try/catch/finally
Scala 支持和 Java 类似的 try、catch、finally 语义。不同点在于,catch 块里通常使用 case 来匹配异常类型。
object TestFuture {
def main(args: Array[String]): Unit = {
var num = 1
try {
num = 100/0 // 生成算数异常
} catch {
// :type 表示是否匹配 type 类型
case e: ArithmeticException => println(e.getMessage)
case e: RuntimeException => println(e.getMessage)
// ... 其他方式一并用 case 处理
} finally {
// .. 不论是否处理异常,都会执行这个代码块
println("run finally")
}
}
}
这段代码会触发除零异常,因此输出结果如下:
/ by zero
run finally
为什么这种方式不总是最优
如果只是处理一个同步代码块,try/catch 已经足够直接。但在 RPC、异步调用或多层组合逻辑里,异常分支容易和业务分支交织在一起,代码会越来越难读。原文也明确指出,这类场景更推荐使用 Try 来承载成功或失败结果。
用 Try 包装结果:Success 和 Failure
Try 的核心思路是:把一次操作的结果显式表示为成功或失败,而不是只在出错时抛异常。最基础的写法如下:
import scala.util.{Failure, Success, Try}
object TestFuture {
def main(args: Array[String]): Unit = {
divide(1, 2) match {
case Success(num) => println(num)
case Failure(e) => println(e.getMessage)
}
divide(1, 0) match {
case Success(num) => println(num)
case Failure(e) => println(e.getMessage)
}
}
def divide(a: Int, b: Int): Try[Int] = {
b match {
case 0 => Failure(new ArithmeticException(s"$a divide by zero"))
case _ => Success(a / b)
}
}
}
输出结果:
0
1 divide by zero
这里有两个关键点:
- 操作成功时,返回
Success[T]。 - 操作失败时,返回由
Failure包装的异常对象。
也就是说,调用方不需要在外层再写一层异常捕获,而是可以通过模式匹配把成功和失败分支并列写清楚。
失败时给默认值:getOrElse
如果你的目标不是细分异常,而是“失败就兜底”,那 getOrElse 会更简洁。原文示例如下:
import scala.util.{Failure, Success, Try}
object TestFuture {
def main(args: Array[String]): Unit = {
val result1= divide(6, 2) getOrElse -1
val result2 = divide(6, 0) getOrElse -1
println(s"result1 = $result1, result2 = $result2")
}
def divide(a: Int, b: Int): Try[Int] = {
b match {
case 0 => Failure(new ArithmeticException(s"$a divide by zero"))
case _ => Success(a / b)
}
}
}
输出结果:
result1 = 3, result2 = -1
这个例子表达得很明确:
divide(6, 2)成功,结果是3。divide(6, 0)失败,于是直接回退到默认值-1。
当业务只关心“能不能给出一个可继续使用的结果”时,这种写法通常比手动匹配 Success/Failure 更省事。
只恢复特定异常:recover
还有一种常见需求是:某些异常希望单独处理,其他情况再交给默认逻辑。这个时候可以使用 recover。
import scala.util.{Failure, Success, Try}
object TestFuture {
def main(args: Array[String]): Unit = {
val result = divide(1, 2) recover {
case e: ArithmeticException => println(e.getMessage)
case e: RuntimeException => println(e.getMessage)
case _ => "recover failed"
}
println(result.get)
}
def divide(a: Int, b: Int): Try[Int] = {
b match {
case 0 => Failure(new ArithmeticException(s"$a divide by zero"))
case -1 => Failure(new RuntimeException("runtime failed"))
case _ => Success(a / b)
}
}
}
这里要特别注意原文提到的一点:如果没有异常,结果仍然是 Success[T],只有失败时才会进入 recover 的逻辑。
使用 recover 时要看清返回值类型
recover 的作用不是单纯“打印错误”,而是把失败结果恢复成一个新的成功结果。因此你在 case 里返回的值,应该和原来的成功类型保持一致,便于后续继续使用。
从思路上看,recover 适合这种场景:异常可以分类处理,而且其中一部分异常有明确补偿策略。
怎么选:try/catch 还是 Try
结合这几种写法,可以这样理解:

- 临时处理单个同步代码块,使用
try/catch/finally足够直接。 - 希望把成功与失败统一建模,优先考虑
Try。 - 只要兜底结果,用
getOrElse。 - 只想恢复某些特定异常,用
recover。
对 Scala 项目来说,Try 最大的价值不只是“能处理异常”,而是它把异常从隐式控制流,变成了可以匹配、组合和传递的显式结果。这也是它在异步和链式调用场景里更容易维护的原因。







