Scala 控制结构梳理:if 表达式、for yield、match 与异常处理到底怎么区分

小林子
小林子
发布时间:2026-08-24| 187人浏览过
原创

前言

很多人刚接触 Scala 时,会觉得这些控制结构和 Java 差不多:`if` 用来判断,`for` 用来循环,`try/catch` 用来处理异常。但真正拉开差距的,是 Scala 强调“表达式可以直接返回结果”这一点。下面就按这个思路拆开来看,帮你判断哪些写法只是执行动作,哪些写法更适合函数式编程里的结果组合。

很多人初学 Scala 控制结构时,会把 if、for、match 当成和 Java 类似的“语句工具”,但 Scala 的关键区别恰恰在这里:它们往往不只是执行流程控制,还能直接产出结果。本文按“是否返回值、是否偏向副作用、是否更适合函数式写法”这条线来拆解这些结构,帮你看清 if 结构、if 表达式、for 循环 与 for 表达式 到底差在哪,以及 match、try/catch/finally 为什么也能保持统一的表达式风格。

Scala 控制结构先看全貌

Scala 提供了常见编程语言里都能见到的控制结构:

  • if/then/else
  • for loops
  • try/catch/finally

同时,它也提供了更有 Scala 风格的控制方式:

  • match expressions
  • for expressions

如果只把它们当作“控制流程的语法糖”,会错过 Scala 最有价值的一点:很多结构本身就是表达式,能够返回结果并继续参与后续计算。

为什么说 Scala 里的 if 不只是流程判断

先看几种最常见的 if 写法

if (x == y) {
    doSomething()
}

// another style
if (x == y) doSomething()
if (x == y) {
    doSomething()
} else {
    doSomethingElse()
}
if (x == y) {
    doSomethingForXY()
} else if (x == z) {
    doSomethingForXZ()
} else {
    doSomethingElse()
}

这几种形式看起来和其他语言差别不大,但 Scala 真正值得注意的地方不在语法外观,而在于 if 的返回行为。

if 表达式总会返回一个结果

在 Scala 里,if 不是单纯的“判断后执行代码”,它本身可以返回结果。你可以像普通流程控制那样忽略它的返回值,也可以直接把结果赋给一个变量。在函数式编程中,后一种写法更常见。

val minValue = if (x < y) x else y

这段代码实现的效果和很多语言里的三元运算符类似,因此 Scala 没有再单独设计一个特殊的三元运算符。对读代码的人来说,重点不是“它像不像三元”,而是要意识到:if 在 Scala 中本来就是表达式。

理解面向表达式编程:表达式与语句的分界线

当代码片段能够产出一个值,并且这个值还能继续被传递、赋值或组合时,这种风格通常被称为“面向表达式编程”(expression-oriented programming,EOP)。上面的 minValue 就是典型例子。

与之相对的是语句。语句的主要目的不是返回结果,而是产生副作用,比如打印、写文件、修改外部状态等。

if (a == b) doSomething() // This is a statements, is for side-effects

这里的关键判断标准很实用:

  • 如果代码重点是“算出一个值”,更接近表达式思路;
  • 如果代码重点是“触发一个动作”,更接近语句和副作用场景。

for 循环和 foreach:适合做遍历,也常用于副作用

for 循环最常见的几种用法

for 循环最直接的用途,就是遍历容器中的元素:

val nums = Seq(1, 2, 3, 4)          // given a sequence of integers
for (n <- nums) println(n)

它也可以配合可变变量执行累加这类操作:

var i = 0
var sum = 0
for (i <- 1 to 100) sum += i
println(sum)                        // 5050

对于 Map 这样的键值容器,同样可以直接遍历键值对:

val scores = Map(
  "Andrew" -> 100,
  "Tom" -> 95,
  "Pater" -> 60
)

for ((name, score) <- scores) println(s"Name: $name, Score: $score")

这类写法都很直观,但要注意,它们往往服务于打印、累加、更新状态等操作,因此通常更偏向副作用。

只为遍历处理时,foreach 往往更直接

如果目标只是遍历容器并执行某个动作,Scala 里也可以直接用 foreach。大多数容器类型都支持它,Map 也不例外。

val nums = Seq(1, 2, 3, 4)
nums.foreach(println)
val scores = Map(
  "Andrew" -> 100,
  "Tom" -> 95,
  "Pater" -> 60
)

scores.foreach {
  case (name, score) => println(s"Name: $name, Score: $score")
}

实践里可以这样理解:

  • 只是遍历并顺手打印、记录、更新状态时,for 和 foreach 都能完成任务;
  • 如果你需要“从一个容器得到另一个容器”,那就该考虑下一节的 for expression 了。

for 表达式和 for 循环不是一回事

很多人第一次接触 Scala 时,容易把 for 统一理解成循环。但在 Scala 里,for 语句 和 for 表达式 的用途并不一样。

用白底信息图对比 Scala 中表达式与副作用导向写法,帮助读者区分 if、for、foreach、for yield 的角色
Scala 控制结构的两条主线这张图把“返回结果”和“产生副作用”两条线并排展开,适合快速建立 Scala。

前面那些遍历打印的例子,本质上都是为了副作用服务;而 for expression 更适合“基于已有容器,产出一个新容器”的场景,这更符合函数式编程的思路。

val nums = Seq(1, 2, 3, 4 )
val doubleNums = for (n <- nums) yield n * 2            // doubleNums: (2, 4, 6, 8)

// using a block of code
val doubleNums = for (n <- nums) yield { n * 2 }        // doubleNums: (2, 4, 6, 8)

这里的 yield 是理解重点。它表示:我要根据现有容器中的元素,计算并产出一个新的容器。

使用 yield 时有两个实用判断

  • yield 后面既可以是一个简洁表达式,也可以是一个大括号包裹的代码块;
  • 如果 yield 后逻辑已经复杂到需要写很多行,通常更适合把复杂逻辑抽成一个方法,让这里保留清晰的表达式。

一句话概括这节内容:

  • for 用来做遍历、打印、累加时,更像语句;
  • for ... yield 用来生成新结果时,才是更典型的表达式写法。

match 表达式:不只是 switch 的替代品

最基础的 match 返回值示例

Scala 的 match 常被拿来和 Java 的 switch 对比,但它更重要的特点是:match 本身也可以返回值。

展示 Scala match 表达式的常见匹配方式,包括固定值、多值合并、守卫条件和范围或字段判断
match 表达式常见用法速览这张图把 match 的四种常见写法拆开,便于理解 `|`、`if`。
val i = 5
val numberDescription = i match {
  case 1 => "equals one"
  case 2 => "equals two"
  case 11 => "equals three"
  case _ => "else number"
}
println(numberDescription)            // else number

当 i 命中某个 case 时,对应分支的结果就会作为整个 match 表达式的返回值。这里的 _ 表示兜底分支,作用类似 Java 中的 default。

这也是 Scala 风格统一的一部分:不管是 if 还是 match,都尽量可以把“分支判断”写成“结果选择”。

顺手看一个 Scala 方法定义

先看一个用 if 作为方法体的例子:

def convertBooleanToStringMessage(bool: Boolean): String = {
  if (bool) "true" else "false"        
}

val result = convertBooleanToStringMessage(true)    // true

这个方法把一个 Boolean 值转换成对应的字符串结果。这里的方法体仍然是表达式思路:根据条件,返回一个 String。

把 match 直接写成方法体

同样的逻辑,也可以改写成 match 表达式:

def convertBooleanToStringMessage(bool: Boolean): String = bool match {
  case true => "true"
  case false => "false"
}

这类写法在分支较多、匹配条件更清晰时通常更好读。

一个 case 可以匹配多个值

在 Scala 中,一个 case 可以用 | 组合多个“值匹配”规则:

val evenOrOdd = i match {
  case 1 | 3 | 5 | 7 | 9 => println("odd")
  case 2 | 4 | 6 | 8 | 10 => println("even")
  case _ => println("some other number")
}

这里的 | 表示值层面的“或者”。如果要组合的是多个布尔表达式条件,则通常会在守卫条件里使用 ||。

在 case 里配合 if,模式匹配会更强

match 配合 if 守卫条件后,就不只是简单的“值相等匹配”,而是可以写出更灵活的模式判断。

count match {
  case 1 => println("one, a lonely number")
  case x if x == 2 || x == 3 => println("two's company, three's a crowd")
  case x if x > 3 => println("4+, that's a party")
  case _ => println("i'm guessing your number is zero or less")
}

在这个例子里,count 先被绑定到变量 x,然后再通过 if 守卫补充判断条件。这里用到的 ||,表示多个表达式条件之间的“或者”。

范围判断和字段判断也能放进 match

对于数值区间,case + if 也很常见:

i match {
  case a if 0 to 9 contains a => println("0-9 range: " + a)
  case b if 10 to 19 contains b => println("10-19 range: " + b)
  case c if 20 to 29 contains c => println("20-29 range: " + c)
  case _ => println("Hmmm...")
}

在实际业务代码里,更常见的是对对象字段做判断:

i match {
  case x if (x.symbol == "XYZ" && x.price < 20) => buy(x)
  case x if (x.symbol == "XYZ" && x.price > 50) => sell(x)
  case x => doNothing(x)
}

这也是模式匹配在 Scala 中非常实用的一面:它既能保持分支结构清晰,又能把条件判断组织得更集中。

try/catch/finally 在 Scala 里如何保持一致风格

用 case 匹配不同异常类型

Scala 同样支持 try/catch/finally 来处理异常,但它的 catch 写法延续了前面 match 的思路:通过 case 匹配不同异常类型。

var fileText = ""
try {
  fileText = openAndReadAFile(fileName)
} catch {
  case e: FileNotFoundException => println("Couldn't find that file.")
  case e: IOException           => println("Had an IOException trying to read that file")
}

这段代码里,openAndReadAFile 用于打开并读取文件,再把结果赋值给 fileText。由于 Scala 使用 Java 的 java.io.* 类进行文件操作,因此这里最常见的异常就是 FileNotFoundException 和 IOException。

finally 一般用来释放资源

在异常处理中,finally 通常承担资源清理职责,比如关闭文件句柄、数据库连接等:

try {
  // your scala code here
} 
catch {
  case e: FooException => handleFooException(e)
  case e: BarException => handleBarException(e)
  case _: Throwable => println("Got some other kind of Throwable exception")
} finally {
  // your scala code here, such as closing a database connection
  // or file handle
}

和前文放在一起看,你会发现 Scala 的控制结构有一个很统一的设计倾向:无论是条件判断、模式匹配还是异常处理,都尽量让代码围绕“分支对应的结果或处理逻辑”展开,而不是把结构写得过于零散。

怎么快速区分这些控制结构该怎么用

如果你只想先抓住最实用的判断标准,可以记住下面这几条:

  • if 在 Scala 中是表达式,可以直接返回值;
  • 只做打印、累加、状态更新时,for 和 foreach 更偏副作用;
  • 要从一个容器生成另一个容器时,用 for ... yield;
  • match 不只是 switch 替代品,它也能返回值,还能结合 if 守卫写复杂匹配;
  • try/catch/finally 则通过 case 继续保持统一的分支风格。

掌握这条主线后,再看 Scala 控制结构时,重点就不再是“语法像不像别的语言”,而是“这段代码是在产生副作用,还是在构造一个结果”。这正是很多 Scala 代码看起来更函数式的原因。

参考链接

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。