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

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

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

思维导图

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

一、流程控制结构

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

分支结构 (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 表达式的返回值是被执行分支中最后一个表达式的值。

代码案例:

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

块表达式 ({}):

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

代码案例:

val a =5val b =10val 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 10if 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 =5while(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 2.x 环境中有效var j =5do{ println(j) j -=1}while(j >0)

循环中断

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

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

代码案例 (模拟 break):

importscala.util.control.Breaks._ println("寻找第一个大于5的偶数:") breakable {for(i <-1 to 10){if(i %2==0&& i >5){ println(s"找到了: $i") break // 中断 breakable 代码块,即跳出循环}}}

代码案例 (模拟 continue):

importscala.util.control.Breaks._ println("\n打印 1到10 之间的所有奇数:")for(i <-1 to 10){ breakable {// 这个 breakable 块放在循环内部,用于模拟 continueif(i %2==0){ break // 当 i 是偶数时,跳出内部的 breakable 块} println(s"奇数: $i")// 这行代码只在 i 是奇数时执行}}

三、综合案例:打印九九乘法表

这个经典案例很好地展示了嵌套循环和字符串格式化的应用。

方法一:传统嵌套 for 循环

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

方法二:使用分号的紧凑型嵌套 for 循环

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 =trueval result =if(condition)100else"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 =-5val sign =if(num >0)"positive"elseif(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 100if i %7==0){ println(i)}
解析:until 创建了一个不包含上界 (100) 的范围,循环守卫 if i % 7 == 0 过滤出了 7 的倍数。

答案四:

for(i <-1 to 50if 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 =0var i =1while(i <=100){ sum += i i +=1} println(s"The sum is: $sum")
解析: 经典的 while 循环用法,需要一个可变的循环控制变量 i 和累加变量 sum

答案八:

importscala.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 =3val y =4val 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

答案十四:

importscala.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")}elseif(i %3==0){ println("Fizz")}elseif(i %5==0){ println("Buzz")}else{ println(i)}}
解析: 这是一个经典的编程问题。关键在于首先检查能被 15 (3和5的最小公倍数) 整除的条件,因为如果先检查3或5,那么15的倍数就会被错误地归类。

日期:2025年9月3日
专栏:Scala教程

Read more

Java调用UniHttp接口请求失败?一次开源的深入实践-百度SN签名认证场景下参数乱序问题的三种解决策略

Java调用UniHttp接口请求失败?一次开源的深入实践-百度SN签名认证场景下参数乱序问题的三种解决策略

目录 前言 一、场景重现 1、UniHttp模式下SN接口定义 2、第一次正式调用 3、第一次遇到211 APP SN校验失败 二、问题查找及开源寻求解决方案 1、抽丝剥茧找到问题所在 2、与作者在Issues的交流 3、开源项目交流有感 三、朝着正确的方向解决问题 1、加密顺序按实际请求参数求解 2、一种兼容Get请求参数动态调整的方法 3、使用注解来设置重排序规则 4、重写请求的QueryParam重排序方法 5、三种方法的使用场景及对比 四、总结 前言         在当今数字化时代,不管是内部系统之间还是跟外部系统的对接,接口调用已成为软件开发中不可或缺的一部分。Java作为一种广泛使用的编程语言,在接口调用方面有着丰富的应用。在现代软件架构中,分布式系统和微服务架构的广泛应用使得不同模块之间的通信主要依赖于接口调用。在之前的博文中,我们讲解了许多跟第三方接口对接的详细案例,比如如何调用实时航班数据、接入百度地图、高德地图、天地图、实时景区等多源数据,也讲解了一些数据接入方式,

By Ne0inhk
Java 大视界 -- Java 大数据在智能交通高速公路收费系统优化与通行效率提升实战(429)

Java 大视界 -- Java 大数据在智能交通高速公路收费系统优化与通行效率提升实战(429)

Java 大视界 -- Java 大数据在智能交通高速公路收费系统优化与通行效率提升实战(429) * 引言: * 正文: * 一、高速收费系统的三大核心痛点与数据瓶颈 * 1.1 传统收费模式的效率天花板 * 1.2 数据孤岛导致的 “盲态运营” * 1.3 计费准确性与异常检测难题 * 1.4 优化前核心指标(数据来源:交通运输部 2022 年公开数据 + 某省运营统计) * 二、Java 大数据技术栈选型与架构设计 * 2.1 技术选型核心原则 * 2.2 核心技术栈详解(生产环境验证版) * 2.3 整体架构设计(Java 大数据驱动的收费系统架构) * 三、核心优化方案与 Java 大数据实战实现 * 3.1 实时车流预测与车道动态调度(

By Ne0inhk
华为OD机试真题2025双机位A卷 Python&JavaScript 实现【采购订单】

华为OD机试真题2025双机位A卷 Python&JavaScript 实现【采购订单】

目录 题目 思路 Code 题目 在一个采购系统中,采购申请(PR)需要经过审批后才能生成采购订单(PO)。每个PR包含商品的单价(假设相同商品的单价一定是一样的)及数量信息。系统要求对商品进行分类处理:单价高于100元的商品需要单独处理,单价低于或等于100元的相同商品可以合并到同一采购订单PO中。针对单价低于100的小额订单,如果量大可以打折购买。 具体规则如下: 如果PR状态为"审批通过",则将其商品加入到PO中。如果PR的状态为"审批拒绝"或"待审批",则忽略改PR,对于单价高于100元的商品、每个商品单独生成一条PO记录。对于单价低于100元的商品,将相同商品的数量合并四到一条PO记录中。如果商品单价<100且商品数量>=100,则单价打9折。 输入描述 第一行包含整数N,表示PR的数量。 接下来N行,每行包含四个用空格分割的整数,按顺序表示:商品ID,

By Ne0inhk