很多人初学 Scala 控制结构时,会把 if、for、match 当成和 Java 类似的“语句工具”,但 Scala 的关键区别恰恰在这里:它们往往不只是执行流程控制,还能直接产出结果。本文按“是否返回值、是否偏向副作用、是否更适合函数式写法”这条线来拆解这些结构,帮你看清 if 结构、if 表达式、for 循环 与 for 表达式 到底差在哪,以及 match、try/catch/finally 为什么也能保持统一的表达式风格。
Scala 控制结构先看全貌
Scala 提供了常见编程语言里都能见到的控制结构:
if/then/elsefor loopstry/catch/finally
同时,它也提供了更有 Scala 风格的控制方式:
match expressionsfor 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 表达式 的用途并不一样。

前面那些遍历打印的例子,本质上都是为了副作用服务;而 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 本身也可以返回值。

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 代码看起来更函数式的原因。
参考链接
- [1] Side Effects, en.wikipedia.org/wiki/Side_e…
- [2] Pattern Matching, en.wikipedia.org/wiki/Patter…









