Scala 循环(一文讲透)

Scala 循环:从入门到实战的完整指南

你有没有遇到过这样的场景?需要对一组数据进行重复处理,比如计算数组中所有数字的总和,或者遍历一个用户列表发送通知。在编程中,这类需求非常常见,而 Scala 提供了强大且优雅的循环机制来应对这些任务。今天,我们就来系统地学习 Scala 循环的用法,无论你是刚接触 Scala 的初学者,还是已有 Java 或 Python 经验的中级开发者,这篇文章都能帮你建立起清晰的认知框架。

Scala 的循环设计深受函数式编程思想影响,它不像传统语言那样依赖 forwhile 的语句结构,而是更强调“可读性”和“安全性”。我们不会直接使用传统的可变循环,而是通过 for 表达式和高阶函数来实现。这种设计不仅减少了副作用,也让代码更简洁、更易维护。

接下来,我们将从基础语法开始,逐步深入到实际应用,带你掌握 Scala 循环的核心能力。

基础语法:for 表达式与 yield 的协作

在 Scala 中,for 不是简单的循环语句,而是一种表达式(expression),它的结果可以被赋值给变量,也可以作为函数返回值。这与 Java 或 C 的 for 语句有本质区别。

我们先看一个最简单的例子:

for (i <- 1 to 5) {
  println(s"当前数字是: $i")
}

这段代码的含义是:从 1 到 5(包含 5)的整数序列中,依次取出每个数字赋给变量 i,然后执行大括号内的代码块。注意,to 是闭区间,包含 5,而 until 是开区间,不包含右边界。

但如果你希望 for 表达式返回一个集合,比如把每个数字乘以 2 后生成新列表,就需要用到 yield 关键字。

val doubled = for (i <- 1 to 5) yield i * 2
println(doubled) // 输出: Vector(2, 4, 6, 8, 10)

这里的关键在于:yieldfor 表达式不再只是执行副作用(如打印),而是构建一个新集合。你可以把 for 表达式想象成一个“数据流水线”——输入原始数据,经过处理,输出结果。

💡 小贴士:tountil 的区别就像“包含终点”和“不包含终点”的两种方式。例如 1 to 31, 2, 3,而 1 until 31, 2

多重循环与生成器:模拟嵌套循环的优雅写法

在处理二维数据或多个集合组合时,传统的嵌套 for 循环容易造成代码冗长且难以阅读。Scala 提供了更简洁的多重生成器语法。

比如,我们想生成所有两个数字相加等于 5 的组合:

val pairs = for {
  i <- 1 to 3
  j <- 1 to 3
  if i + j == 5 // 这是过滤条件,不是嵌套结构
} yield (i, j)

println(pairs) // 输出: Vector((2,3), (3,2))

这里的关键是:for 块中可以写多个生成器(i <- 1 to 3j <- 1 to 3),它们会自动组合,形成笛卡尔积。而 if 条件写在生成器之后,用于过滤结果。

你可以把这种写法理解为“先生成所有可能的组合,再筛出符合条件的”。它比写嵌套 for 更清晰,也更安全。

生成器类型 说明
i <- 1 to 5 生成整数序列,闭区间
i <- 1 until 5 生成整数序列,开区间,不包含 5
i <- List(1, 2, 3) 遍历列表中的每个元素
i <- Option(42) 只有存在值时才进入循环体

注意:如果某个生成器返回 None,整个循环将不会执行。这在处理可选值时非常有用。

条件过滤:if 语句在 for 表达式中的应用

在实际开发中,我们很少需要遍历所有数据,更多时候是“只处理满足条件的”。Scala 的 for 表达式支持在生成器之后添加 if 条件,实现“过滤”功能。

例如,我们想从一个学生名单中找出年龄大于 18 的学生:

case class Student(name: String, age: Int)

val students = List(
  Student("张三", 20),
  Student("李四", 17),
  Student("王五", 22)
)

val adults = for {
  student <- students
  if student.age > 18
} yield student.name

println(adults) // 输出: List(张三, 王五)

这里 if student.age > 18 就是一个过滤条件。只有当条件为真时,对应的 student 才会被加入结果集合。

这种写法的优雅之处在于:它把“生成”和“过滤”逻辑写在同一个块中,逻辑清晰,可读性强。相比之下,使用 filter 方法虽然功能相同,但需要额外调用函数,代码略显冗长。

⚠️ 注意:if 条件必须写在生成器之后,否则会报错。这是 Scala 的语法限制,也是为了防止逻辑混乱。

遍历集合与数组:从 List 到 Map 的实践

Scala 的循环可以处理几乎所有集合类型,包括 ListVectorSetMap 等。下面我们用 Map 来展示一个实际案例。

假设我们有一个商品价格表,想找出所有价格超过 100 元的商品:

val prices = Map(
  "苹果" -> 5.5,
  "香蕉" -> 8.0,
  "手机" -> 3999.0,
  "耳机" -> 150.0
)

val expensiveItems = for {
  (name, price) <- prices
  if price > 100
} yield name

println(expensiveItems) // 输出: List(手机, 耳机)

这里的关键是:for 表达式支持模式匹配。 (name, price) <- prices 会自动拆解 Map 的键值对,分别赋值给 nameprice。这种语法非常直观,也符合函数式编程的“解构赋值”理念。

再看一个 List 的例子,我们想生成一个从 1 到 10 的平方列表:

val squares = for (i <- 1 to 10) yield i * i
println(squares) // 输出: Vector(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

这类操作在数据处理中非常常见。你可以把 for 表达式当作一个“转换器”——输入一组数据,输出另一组数据。

高级技巧:嵌套 for 表达式与函数式替代方案

虽然 for 表达式非常强大,但在某些场景下,使用高阶函数(如 mapfilterflatMap)可能更合适。这并不是说 for 不好,而是要根据场景选择最合适的工具。

例如,上面的“生成所有满足条件的组合”也可以写成:

val pairs = (1 to 3).flatMap(i => 
  (1 to 3).filter(j => i + j == 5).map(j => (i, j))
)

println(pairs) // 输出: List((2,3), (3,2))

虽然功能一致,但 flatMap + filter + map 的写法更接近函数式编程的风格,强调“变换”而非“循环”。

但如果你的逻辑复杂,比如涉及多个条件和嵌套结构,for 表达式依然更清晰。比如:

val result = for {
  user <- users
  if user.isActive
  order <- user.orders
  if order.amount > 100
  product <- order.items
  if product.category == "电子产品"
} yield product.name

这段代码清晰地表达了“从活跃用户中,找出订单金额大于 100 且包含电子产品订单的所有产品名称”。用 for 表达式来写,逻辑一目了然。

实战案例:处理学生成绩与统计分析

我们来做一个综合案例:假设我们有一组学生成绩数据,需要完成以下任务:

  • 找出所有及格(>= 60)的学生
  • 计算平均分
  • 统计优秀(>= 90)人数
case class Student(name: String, score: Int)

val students = List(
  Student("张三", 88),
  Student("李四", 55),
  Student("王五", 92),
  Student("赵六", 76)
)

// 1. 找出及格的学生
val passed = for (s <- students if s.score >= 60) yield s.name
println(s"及格学生: ${passed.mkString(", ")}")

// 2. 计算平均分
val avgScore = students.map(_.score).sum / students.size.toDouble
println(s"平均分: $avgScore")

// 3. 统计优秀人数
val excellentCount = students.count(_.score >= 90)
println(s"优秀人数: $excellentCount")

输出结果:

及格学生: 张三, 王五, 赵六
平均分: 77.5
优秀人数: 1

这个例子展示了 for 表达式在数据处理中的实际价值:它不仅能过滤,还能配合 mapcountsum 等函数,完成复杂的数据分析任务。

总结:掌握 Scala 循环的核心思想

通过本文的学习,你应该已经掌握了 Scala 循环的核心机制。与传统语言不同,Scala 的 for 表达式不是“控制流语句”,而是一种“数据变换表达式”。它强调“生成”和“过滤”,而不是“执行副作用”。

记住几个关键点:

  • 使用 yield 可以让 for 表达式返回集合
  • 多重生成器自动组合,支持 if 过滤
  • 支持模式匹配,可直接解构 MapTuple
  • mapfilter 等函数式方法结合使用,灵活高效

最后提醒一句:Scala 循环的设计理念是“用表达式代替语句”,这不仅能减少错误,还能让代码更易测试和复用。当你开始使用 Scala 循环时,不妨多尝试用 for 表达式替代传统的 for 循环,你会发现代码变得更简洁、更安全。