二、Scala流程控制:分支与循环

简介: 在Scala里,连if判断和for循环这些基本功都被赋予了“超能力”。if语句不仅能做判断,还能像三元运算符一样直接“返回值”,让代码更紧凑。而for循环更是个“全能选手”,不仅能遍历范围,还能加上if守卫做筛选,最厉害的是配合yield关键字,能直接把循环结果“变”成一个全新的集合。至于while循环虽然还在,但像break这种命令,Scala则更推荐你用函数式的思路去解决。

在掌握了 Scala 的基础语法和数据类型之后,我们接下来将深入学习如何控制程序的执行流程。本节将详细探讨 Scala 中的分支和循环结构,你将领略到 Scala 在流程控制方面独特的、富有表现力的设计。

思维导图

image.png

image.png

image.png

一、流程控制结构

顺序结构: 代码默认按照从上到下、从左到右的顺序执行。
这是 最基本的执行模式, 无需特殊语法。

分支结构 (if): 根据条件决定代码执行路径。
if 语句用于 基于一个 布尔表达式结果选择性地执行 代码块

| 分支类型 | 语法 | 示例 |
| :--- | :--- | :--- |
| 单分支 | if (condition) { ... } | if (age >= 18) { println("Adult") } |
| 双分支 | if (condition) { ... } else { ... } | if (score >= 60) { println("Pass") } else { println("Fail") } |
| 多分支 | if ... else if ... else ... | if (grade == 'A') { ... } else if (grade == 'B') { ... } else { ... } |
| 嵌套分支 | 在一个 if 结构中嵌入另一个 | if (isLogin) { if (isAdmin) { ... } } |

if 表达式: Scala 中的 if 语句是表达式,具有返回值。这个特性可以用来替代 Java 中的三元运算符。
if 表达式返回值被执行分支最后一个表达式的值。

代码案例:
scala // 单分支 val age = 20 if (age >= 18) { println("此人是成年人 (Adult)") } // 双分支 val score = 55 if (score >= 60) { println("考试通过 (Pass)") } else { println("考试未通过 (Fail)") } // 多分支 val grade = 'B' if (grade == 'A') { println("优秀 (Excellent)") } else if (grade == 'B') { println("良好 (Good)") } else if (grade == 'C') { println("及格 (Average)") } else { println("需要努力 (Needs Improvement)") } // 嵌套分支 val isLogin = true val isAdmin = false if (isLogin) { println("用户已登录。") if (isAdmin) { println("欢迎管理员!拥有所有权限。") } else { println("欢迎普通用户!拥有受限权限。") } } else { println("用户未登录,请先登录。") } // if表达式返回值 val temperature = 25 val weatherDescription = if (temperature > 30) { "炎热" } else if (temperature < 10) { "寒冷" } else { "温和" } println(s"今天天气: $weatherDescription") } } val x = 10 // 使用 if 表达式给常量赋值 val result = if (x > 0) "positive" else "non-positive" println(result) // 输出: positive val score = 85 val grade = if (score >= 90) 'A' else if (score >= 80) 'B' else 'C' println(s"Grade is: $grade") // 输出: Grade is: B

块表达式 ({}):
使用花括号 {} 包围的多行代码构成一个块表达式 块表达式的 是其 最后一个语句的值。

代码案例:
scala val a = 5 val b = 10 val blockResult = { println("Calculating...") val sum = a + b sum // 块表达式的最后一个语句是 sum, 所以它的值被赋给 blockResult } println(s"The result from the block is: $blockResult") // 输出 15

## 二、循环结构

for循环

Scala 的 for 循环远比传统命令式语言的 for 循环更灵活,它更像一个“生成器”

for 循环特性 语法/示例 说明
遍历范围 (Range) for (i <- 1 to 10)
for (i <- 1 until 10)
to 包含上界 (1-10)。
until 不包含上界 (1-9)。
循环守卫 (Guard) for (i <- 1 to 10 if i % 2 == 0) 在遍历过程中加入 if 条件进行过滤,只有满足条件的元素才会被处理。
嵌套循环 for (i <- 1 to 3; j <- 1 to 3) 可以将多个生成器分号隔开写在同一行,代码更紧凑
for 推导式 (Comprehension) val squares = for (i <- 1 to 5) yield i * i 使用 yield 关键字,将 for 循环的每次迭代结果收集起来,构建成一个新的集合

代码案例:

// 遍历范围并使用循环守卫
println("偶数 in 1 to 10:")
for (i <- 1 to 10 if i % 2 == 0) {
  print(s"$i ")
}
println()

// 嵌套循环打印坐标
println("坐标:")
for (i <- 1 to 2; j <- 'a' to 'b') {
  println(s"($i, $j)")
}

// for 推导式生成一个 List[Int]
val squares = for (i <- 1 to 5) yield i * i
println(s"Squares: $squares") // 输出: Squares: List(1, 4, 9, 16, 25)

while循环

格式: while (condition) { ... }

var i = 5
while (i > 0) {
  println(i)
  i -= 1
}

do-while循环

语法 (Scala 2):

// do {
//   // 循环体
// } while (condition)
重要说明: do-while 语句在 Scala 3 中已被移除 由于它在实践中 使用较少不符合函数式编程的 表达式风格,Scala 3 将其废弃。任何 do-while 逻辑都 可以while 循环或其他结构 等价实现

代码案例 (Scala 2):
scala // 以下代码仅在 Scala 2.x 环境中有效 var j = 5 do { println(j) j -= 1 } while (j > 0)

### 循环中断
Scala 本身没有内置的 breakcontinue 关键字,因为这 鼓励使用函数式的编程风格 (如使用过滤器、递归等)。但如果 确实需要,可以 通过标准库模拟。

> 标准做法: 导入 scala.util.control.Breaks,并使用 breakablebreak() 方法。
实现 break: 将 整个循环breakable 代码块包裹。
实现 continue: 将 循环体内部需要跳过的部分用 breakable 包裹。

代码案例 (模拟 break):
scala import scala.util.control.Breaks._ println("寻找第一个大于5的偶数:") breakable { for (i <- 1 to 10) { if (i % 2 == 0 && i > 5) { println(s"找到了: $i") break // 中断 breakable 代码块,即跳出循环 } } }
代码案例 (模拟 continue):
scala import scala.util.control.Breaks._ println("\n打印 1到10 之间的所有奇数:") for (i <- 1 to 10) { breakable { // 这个 breakable 块放在循环内部,用于模拟 continue if (i % 2 == 0) { break // 当 i 是偶数时,跳出内部的 breakable 块 } println(s"奇数: $i") // 这行代码只在 i 是奇数时执行 } }

## 三、综合案例:打印九九乘法表
这个经典案例 很好地展示了 嵌套循环字符串格式化的应用。

方法一:传统嵌套 for 循环
scala println("--- 九九乘法表 (传统嵌套 for) ---") for (i <- 1 to 9) { for (j <- 1 to i) { print(s"$j * $i = ${i * j}\t") } println() // 每行结束后换行 }

方法二:使用分号的紧凑型嵌套 for 循环
scala println("\n--- 九九乘法表 (紧凑型 for) ---") for (i <- 1 to 9; j <- 1 to i) { print(s"$j * $i = ${i * j}\t") if (j == i) println() // 当内层循环结束时换行 }

解析:这种 紧凑写法两个生成器放在 同一行if (j == i) 这个 判断关键,它 确保了只在 每一行最后一个表达式 打印完毕后才执行 换行操作。


---



## 练习题

题目一:if 表达式
编写一段代码,使用 if 表达式判断一个整数 num 是正数、负数还是零,并将结果 ("positive", "negative", "zero") 赋值给一个 val 变量 sign

题目二:for 循环与 to
使用 for 循环打印出从 5 到 1 (递减) 的所有整数。

题目三:for 循环与 until
使用 for 循环打印出 100 以内的所有 7 的倍数 (不包含100)。

题目四:循环守卫
使用 for 循环和循环守卫,找出 1 到 50 之间所有既能被 3 整除又能被 5 整除的数。

题目五:嵌套 for 循环
使用嵌套的 for 循环打印一个 4x4 的星号 * 矩阵。

题目六:for 推导式 (生成新集合)
使用 for 推导式,将一个 List("apple", "banana", "cherry") 转换为一个新的 List,其中每个单词都变成大写。

题目七:while 循环
使用 while 循环计算 1 到 100 的所有整数之和。

题目八:模拟 break
给定一个 List(1, 4, 9, 16, 25, 36, 49),使用 breakablebreak 找到并打印出第一个大于 30 的数后立即停止循环。

题目九:块表达式返回值
编写一个块表达式,它接收两个变量 xy,在内部计算它们的平方和 (x*x + y*y),并将这个结果作为块表达式的值赋给一个 val 变量 result

题目十:if 表达式的类型
分析以下代码,result 的类型会被推断为什么?并解释原因。

val condition = true
val result = if (condition) 100 else "Error"

题目十一:for 循环步长
使用 for 循环和 by 关键字,打印出从 0 到 20,步长为 5 的所有数字 (0, 5, 10, 15, 20)。

题目十二:for 推导式与守卫
给定一个 List(1, 2, 3, 4, 5, 6),使用 for 推导式和循环守卫,生成一个新的 List,其中只包含原列表中偶数的平方。

题目十三:if 表达式与 Unit 类型
分析以下代码,result 的值和类型是什么?

val result = if (false) "Success"

题目十四:模拟 continue
使用 breakablebreak 模拟 continue 的效果:打印 1 到 10 中所有的奇数。

题目十五:综合应用 (FizzBuzz)
编写一个程序,使用 for 循环遍历 1 到 100。对于每个数字,使用 if-else if-else 结构判断:

  • 如果数字能被 15 整除,打印 "FizzBuzz"。
  • 如果只能被 3 整除,打印 "Fizz"。
  • 如果只能被 5 整除,打印 "Buzz"。
  • 否则,打印数字本身。

答案与解析

答案一:

val num = -5
val sign = if (num > 0) "positive" else if (num < 0) "negative" else "zero"
println(s"The sign of $num is: $sign")

解析: if-else if-else 结构作为表达式,其结果可以直接赋值给 sign 变量。

答案二:

for (i <- 5 to 1 by -1) {
  println(i)
}

解析: for 循环可以通过 by 关键字指定步长,步长为负数时即为递减。

答案三:

for (i <- 1 until 100 if i % 7 == 0) {
  println(i)
}

解析: until 创建了一个不包含上界 (100) 的范围,循环守卫 if i % 7 == 0 过滤出了 7 的倍数。

答案四:

for (i <- 1 to 50 if i % 3 == 0 && i % 5 == 0) {
  println(i)
}

解析: 循环守卫可以包含复杂的逻辑条件,如使用 && (与)。

答案五:

for (i <- 1 to 4) {
  for (j <- 1 to 4) {
    print("* ")
  }
  println() // 每行结束后换行
}

解析: 外层循环控制行数,内层循环控制每行打印的星号数量。

答案六:

val words = List("apple", "banana", "cherry")
val upperWords = for (word <- words) yield word.toUpperCase
println(upperWords) // 输出: List(APPLE, BANANA, CHERRY)

解析: for-yield 结构遍历 words 列表,对每个元素 word 应用 .toUpperCase 方法,并将结果收集到一个新的 List 中。

答案七:

var sum = 0
var i = 1
while (i <= 100) {
  sum += i
  i += 1
}
println(s"The sum is: $sum")

解析: 经典的 while 循环用法,需要一个可变的循环控制变量 i 和累加变量 sum

答案八:

import scala.util.control.Breaks._
val numbers = List(1, 4, 9, 16, 25, 36, 49)
breakable {
  for (num <- numbers) {
    if (num > 30) {
      println(s"Found number greater than 30: $num")
      break
    }
  }
}

解析: breakable 包裹了整个循环,当 num > 30 条件满足时,break 会中断 breakable 块的执行,从而跳出循环。

答案九:

val x = 3
val y = 4
val result = {
  println("Performing calculation...")
  val xSquare = x * x
  val ySquare = y * y
  xSquare + ySquare // 这是块的最后一个表达式,它的值将赋给 result
}
println(result) // 输出 25

解析: 块表达式的值由其最后一个表达式 xSquare + ySquare 决定。

答案十:
result 的类型会被推断为 Any

解析: Scala 的 if-else 表达式的返回类型是两个分支返回类型最近公共父类型100 的类型是 Int"Error" 的类型是 StringIntString 的最近公共父类型是 Any

答案十一:

for (i <- 0 to 20 by 5) {
  println(i)
}

解析: by 关键字用于指定循环的步长。

答案十二:

val numbers = List(1, 2, 3, 4, 5, 6)
val evenSquares = for (n <- numbers if n % 2 == 0) yield n * n
println(evenSquares) // 输出: List(4, 16, 36)

解析: for-yield 结合循环守卫,先通过 if n % 2 == 0 过滤出偶数,然后 yield 出它们的平方。

答案十三:
result 的值是 () (Unit 的实例),类型是 Any

解析: 这个 if 语句没有 else 分支。当条件为 false 时,if 语句没有返回值。在 Scala 中,这种情况的“无返回值”由 Unit 类型表示。因此,整个 if 表达式的类型是 StringUnit 的最近公共父类型,即 Any

答案十四:

import scala.util.control.Breaks._
for (i <- 1 to 10) {
  breakable {
    if (i % 2 == 0) {
      break // 如果是偶数,中断 breakable 块,效果类似 continue
    }
    println(i) // 这行代码只在 i 是奇数时执行
  }
}

解析:breakable 块放在循环体内部。当满足某个条件时调用 break,只会跳出当前的 breakable 块,然后继续下一次循环,从而模拟了 continue 的效果。

答案十五:

for (i <- 1 to 100) {
  if (i % 15 == 0) {
    println("FizzBuzz")
  } else if (i % 3 == 0) {
    println("Fizz")
  } else if (i % 5 == 0) {
    println("Buzz")
  } else {
    println(i)
  }
}

解析: 这是一个经典的编程问题。关键在于首先检查能被 15 (3和5的最小公倍数) 整除的条件,因为如果先检查3或5,那么15的倍数就会被错误地归类。

相关文章
|
28天前
|
存储 Java 编译器
三、Scala方法与函数
在Scala的世界里,方法和函数是两个不同的“物种”。方法(用def定义)就像是焊在工作台(类)上的“固定工具”,你得先有工作台才能用它。而函数(用val和=>定义)则是“便携电动工具”,它本身就是个独立的对象,可以被传来传去,塞进别的工具(高阶函数)里使用。当你需要把“固定工具”拿下来当“便携工具”用时,Scala还提供了一个神奇的_转换符,能帮你轻松搞定。
|
28天前
|
JSON 分布式计算 Java
一、Scala 基础语法、变量与数据类型
入门Scala,你会发现它从一开始就鼓励你写出更“结实”的代码。它推荐你多用val来定义“一次性”常量,少用var定义可变变量,这能减少很多潜在的bug。它的类型推断能让你少写很多代码,而s"你好, ${name}"这样的字符串插值,更是把繁琐的拼接变得无比优雅。再加上它的一切皆对象、.toInt等方便的类型转换,以及聪明的==值比较,让你能快速上手,写出简洁又安全的代码。
|
2月前
|
运维 开发者 Docker
一、Docker:一场颠覆应用部署与运维的容器革命
Docker的出现,就是为了解决“在我电脑上能跑”这个老大难问题。它像个魔法集装箱,把你的程序和它需要的所有东西(比如库、配置)都打包好,这样无论在哪运行,环境都一模一样。理解它很简单,就三个核心玩意儿:镜像是程序的“安装包”,容器是跑起来的程序,而仓库就是存放和分享这些“安装包”的地方。
|
3月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
二、Linux文本处理与文件操作核心命令
|
3月前
|
存储 安全 Linux
三、Linux用户与权限管理详解
管理Linux系统就像当一个大楼的管家。首先,你得用useradd和passwd给新员工发“钥匙”(创建用户并设密码),并用groupadd把他们分到不同“部门”(用户组)。然后,你要为每个“房间”(文件或目录)设定规矩,这就是文件权限:用chmod命令设置谁(所有者、同部门、其他人)可以“进入”(x)、“读取”(r)或“写入”(w)。最后,用chown还能把房间的归属权转让给别人。
|
3月前
|
安全 Linux Shell
四、Linux核心工具:Vim, 文件链接与SSH
要想在Linux世界里游刃有余,光会“走路”还不够,还得配上几样“高级装备”。首先是Vim编辑器,它像一把瑞士军刀,让你能在命令行里高效地修改文件。然后要懂“软硬链接”,软链接像个快捷方式,硬链接则是给文件起了个别名。最后,SSH是你的“传送门”,不仅能让你安全地远程登录服务器,还能用scp轻松传输文件,设置好密钥更能实现免-密登录,极大提升效率。
|
29天前
|
Ubuntu Shell Linux
二、Docker安装部署教程
当你敲下docker run时,背后发生了一系列神奇的操作:从检查本地镜像,到从仓库拉取,再到创建并启动容器。搞懂这个核心流程后,就可以动手在Linux上安装Docker了。关键一步是先添加官方的软件源,然后再安装。为了避免拉取镜像时龟速等待,最后一定要记得配置国内的镜像加速器,这能极大提升你的使用体验。
二、Docker安装部署教程
|
3月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
703 57
|
28天前
|
缓存 Java Maven
六、Docker 核心技术:Dockerfile 指令详解
想亲手给你的应用程序打造一个专属的“集装箱”吗?Dockerfile就是你的说明书!它其实就是一个简单的文本文件,你可以在里面像搭积木一样,用FROM、COPY、RUN这些指令,一步步告诉Docker如何打包你的应用。最后,通过多阶段构建的小技巧,还能给镜像“减肥”,让它变得轻巧又高效。快来学习用Dockerfile变身打包达人吧!
|
28天前
|
应用服务中间件 Shell nginx
七、Docker核心技术:深入理解网络模式 (Bridge, Host, None, Container)
容器不仅仅是孤立的运行环境,它们需要相互通信,也需要与外部世界进行交互。理解 Docker 的不同网络模式,是构建和部署复杂多容器应用的关键。本节将深入探讨 Docker 原生提供的四种网络模式以及强烈推荐使用的自定义网络。要让它们通信,需要将其中一个容器也连接到另一个网络上。默认 bridge 网络不支持容器名DNS解析,只能通过IP地址通信。容器没有自己的独立IP地址,它共享宿主机的IP。网络模式启动一个容器后,如何查看该容器的IP地址?时,该容器默认会连接到哪个网络?模式运行,并且其内部的应用监听。