Scala循环与判断:一切皆表达式的控制流

简介: 本文详解Scala控制流的“表达式思维”:if、for、match、try等皆为有返回值的表达式,而非Java式的语句。通过对比Java命令式写法,揭示Scala如何用for推导式生成集合、用match替代switch、用Try处理异常,助Java开发者完成函数式思维跃迁。(239字)

Java的if是语句,Scala的if是表达式;Java的for是循环,Scala的for是推导式。理解Scala控制流的"表达式思维",是写出地道Scala代码的关键。

如果你从Java转到Scala,写第一个if语句时可能会困惑:为什么if后面没有分号?为什么for循环里有个yield?为什么没有break和continue?

这些困惑的根源在于:Java的控制结构是"语句",而Scala的控制结构是"表达式"。

Java里,if是一条语句,你需要单独声明变量再赋值;for是一条循环语句,用来重复执行代码块;switch是一条选择语句,用来分支执行。而Scala里,几乎所有控制结构都是表达式,都有返回值。

这个设计哲学看似微小,却带来了根本性的差异:你可以用if表达式直接赋值、用for推导式生成新集合、用match表达式替代整段if-else链。控制流不再是"命令计算机做什么",而是"描述你想要什么结果"。

这篇文章从七个层面拆解Scala的循环与判断,帮你建立从Java命令式到Scala表达式式的思维转变。

Scala控制流知识体系


01 if表达式:一切皆有返回值

if是表达式,不是语句

Java里,if是语句,你需要这样写:

// Java: if是语句
String result;
if (x > 0) {
   
    result = "positive";
} else {
   
    result = "non-positive";
}

Scala里,if是表达式,直接返回值:

// Scala: if是表达式
val result = if (x > 0) "positive" else "non-positive"

这不是语法糖——这是根本的思维转变。if表达式的结果可以直接赋值、传递、作为函数返回值。

// if表达式作为函数返回值
def classify(n: Int): String = if (n > 0) "positive" else "non-positive"

// if表达式嵌套
val grade = if (score >= 90) "A"
            else if (score >= 80) "B"
            else if (score >= 70) "C"
            else "D"

// if表达式作为参数
println(if (isEmpty) "空列表" else "非空列表")

表达式vs语句的本质区别

维度 Java(语句) Scala(表达式)
if 分支执行代码块 计算并返回一个值
for 重复执行代码块 生成新集合
match 分支执行 计算并返回一个值
try 异常处理 捕获异常并返回值
语句结尾 需要分号 不需要分号

核心原则:在Scala里,能写表达式就不写语句。表达式有返回值,可以组合、可以嵌套、可以传递;语句只是执行副作用。


02 for循环与for推导式

Scala的for比Java强大得多。它不仅是循环,还是推导式(for-comprehension)——可以用来生成新集合。

基础for循环

// to: 包含上界
for (i <- 1 to 5) print(i)     // 12345

// until: 不包含上界
for (i <- 1 until 5) print(i)  // 1234

// 遍历集合
val fruits = List("apple", "banana", "orange")
for (f <- fruits) println(f)

// 带索引遍历
for ((fruit, index) <- fruits.zipWithIndex) {
  println(s"$index: $fruit")
}

守卫(for if)

类似Java的for循环里加if条件:

// 打印1到10的偶数
for (i <- 1 to 10 if i % 2 == 0) println(i)

// 多个守卫
for (i <- 1 to 20 if i % 2 == 0 if i % 3 == 0) println(i)

// 等价写法
for (i <- 1 to 20 if (i % 2 == 0) && (i % 3 == 0)) println(i)

多生成器(嵌套循环)

// 两层嵌套循环
for {
  x <- 1 to 3
  y <- 1 to 3
} println(s"($x, $y)")

// 等价于Java:
// for (int x = 1; x <= 3; x++) {
//     for (int y = 1; y <= 3; y++) {
//         System.out.println("(" + x + ", " + y + ")");
//     }
// }

for推导式(yield)

这是Scala for最强大的特性——用yield收集结果,生成新集合:

// 用yield生成新集合
val squares = for (i <- 1 to 5) yield i * i
// squares: Vector(1, 4, 9, 16, 25)

// 带守卫的推导式
val evenSquares = for (i <- 1 to 10 if i % 2 == 0) yield i * i
// evenSquares: Vector(4, 16, 36, 64, 100)

// 多生成器推导式
val pairs = for {
  x <- List(1, 2, 3)
  y <- List('a', 'b')
} yield (x, y)
// pairs: List((1,a), (1,b), (2,a), (2,b), (3,a), (3,b))

for推导式与map/flatMap的关系

for推导式本质上是map、flatMap、withFilter的语法糖:

// for推导式
val result = for {
  x <- Some(3)
  y <- Some(4)
} yield x + y
// result: Some(7)

// 等价于
val result2 = Some(3).flatMap(x => Some(4).map(y => x + y))
// result2: Some(7)

// 这就是为什么for推导式可以用于Option、List、Future
// 只要类型有map和flatMap,就可以用for推导式

大数据场景提示:Spark的RDD和Dataset都支持map和flatMap,因此也支持for推导式。虽然实际开发中更多直接用链式调用,但理解for推导式的本质,有助于理解Spark转换操作的函数式本质。


03 while与do-while循环

Scala保留了while循环,但不推荐使用——因为while是命令式的、可变状态的,与函数式编程风格相悖。

// while循环
var i = 0
while (i < 5) {
  println(i)
  i += 1
}

// do-while循环
var j = 0
do {
  println(j)
  j += 1
} while (j < 5)

为什么不推荐while?

// 命令式:可变状态,容易出bug
var sum = 0
for (i <- 1 to 100) {
  sum += i
}
// sum: 5050

// 函数式:不可变,声明式
val sum = (1 to 100).sum
// sum: 5050

// 函数式:用递归替代循环
def factorial(n: Int): Int = n match {
  case 0 => 1
  case _ => n * factorial(n - 1)
}

经验法则:在Scala中,优先使用集合操作(map/filter/reduce/fold)和递归,而不是while循环。while只在确实需要循环等待、轮询等场景使用。


04 match模式匹配

match是Scala的switch,但功能强大得多。它支持值匹配、类型匹配、解构匹配、守卫匹配。

基础值匹配

val day = "MON"
val type = day match {
  case "MON" | "TUE" | "WED" | "THU" | "FRI" => "weekday"
  case "SAT" | "SUN" => "weekend"
  case _ => "unknown"
}
// type: "weekday"

// match是表达式,可以直接赋值
val result = x match {
  case 0 => "zero"
  case 1 => "one"
  case _ => "other"
}

类型匹配

def describe(x: Any): String = x match {
  case i: Int => s"整数: $i"
  case s: String => s"字符串: $s"
  case d: Double => s"浮点数: $d"
  case b: Boolean => s"布尔值: $b"
  case list: List[_] => s"列表,长度${list.size}"
  case m: Map[_, _] => s"映射,${m.size}个键值对"
  case _ => "其他类型"
}

describe(42)           // "整数: 42"
describe("hello")      // "字符串: hello"
describe(List(1,2,3))  // "列表,长度3"

样例类解构匹配

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

val alice = Person("Alice", 25)

alice match {
  case Person("Alice", age) => println(s"Alice is $age")
  case Person(name, age) if age < 18 => println(s"未成年: $name")
  case Person(name, _) => println(s"Hello $name")
}

守卫(match if)

def classify(n: Int): String = n match {
  case x if x > 0 => "positive"
  case x if x < 0 => "negative"
  case _ => "zero"
}

// 更复杂的守卫
def greet(person: Person): String = person match {
  case Person(name, age) if age < 18 => s"未成年: $name"
  case Person(name, age) if age >= 65 => s"老年: $name"
  case Person(name, age) => s"成年: $name ($age)"
}

匹配集合

val list = List(1, 2, 3)

list match {
  case Nil => "空列表"
  case head :: Nil => s"单元素: $head"
  case head :: second :: rest => s"多个元素,首:$head, 次:$second"
  case _ => "其他"
}

// 匹配元组
val pair = (1, "hello")
pair match {
  case (1, "hello") => "匹配"
  case (x, y) => s"($x, $y)"
}

match的穷尽性检查

如果match的输入是密封特质(sealed trait),编译器会检查是否所有情况都被覆盖:

sealed trait Color
case object Red extends Color
case object Green extends Color
case object Blue extends Color

def colorName(c: Color): String = c match {
  case Red => "红色"
  case Green => "绿色"
  // 如果漏掉Blue,编译器会警告:match may not be exhaustive
  case Blue => "蓝色"
}

对Java开发者的启示:Java的switch只能匹配整数值和枚举,而Scala的match可以匹配任何类型、可以解构对象、可以加守卫。模式匹配是Scala函数式编程的核心工具,在Spark的RDD处理、Akka的消息路由中大量使用。


05 try-catch-finally表达式

Scala的try-catch也是表达式,可以返回值:

// try-catch作为表达式
val result = try {
  "42".toInt
} catch {
  case e: NumberFormatException => 0
}
// result: 42

// 多catch分支
val config = try {
  val props = new java.util.Properties()
  props.load(getClass.getResourceAsStream("/config.properties"))
  props.getProperty("app.name")
} catch {
  case e: java.io.FileNotFoundException => "默认应用"
  case e: java.io.IOException => "IO错误"
} finally {
  println("加载完成")
}

Try类型:更函数式的异常处理

Scala推荐用Try替代try-catch:

import scala.util.Try

// Try[T]是一个容器,要么是Success[T],要么是Failure
val result = Try("abc".toInt)
// result: Failure(java.lang.NumberFormatException)

// 模式匹配处理Try
result match {
  case Success(value) => println(s"成功: $value")
  case Failure(e) => println(s"失败: ${e.getMessage}")
}

// 链式处理
val safeResult = Try("42".toInt)
  .map(_ * 2)
  .getOrElse(0)
// safeResult: 84

// for推导式中使用Try
val combined = for {
  a <- Try("10".toInt)
  b <- Try("20".toInt)
} yield a + b
// combined: Success(30)

06 循环控制:break与continue

Scala没有原生的break和continue关键字。这是设计选择——鼓励用函数式方式替代循环控制。

用Breaks类实现break

import scala.util.control.Breaks._

breakable {
  for (i <- 1 to 10) {
    if (i == 5) break()
    println(i)
  }
}
// 输出: 1 2 3 4

用函数式方式替代continue

// Java: for循环里用continue跳过某些元素
// for (int i = 1; i <= 10; i++) {
//     if (i % 2 == 0) continue;
//     System.out.println(i);
// }

// Scala: 用filter替代continue
for (i <- 1 to 10 if i % 2 != 0) println(i)

// 更函数式的写法
(1 to 10).filter(_ % 2 != 0).foreach(println)

用return从函数返回

def findFirstEven(nums: List[Int]): Int = {
  for (n <- nums) {
    if (n % 2 == 0) return n  // 直接从函数返回
  }
  -1  // 没找到
}

为什么没有break/continue?

Scala的设计哲学是:用集合操作替代循环,用模式匹配替代分支,用Option替代null。break和continue是命令式循环的产物,当你用map/filter/reduce时,根本不需要它们。

// 命令式:用break找到第一个偶数
import scala.util.control.Breaks._
var found = -1
breakable {
  for (n <- nums) {
    if (n % 2 == 0) {
      found = n
      break()
    }
  }
}

// 函数式:用headOption
val found = nums.find(_ % 2 == 0).getOrElse(-1)

后者更简洁、更安全、更易读。


07 与Java循环判断的对比

Java控制流vs Scala控制流

核心差异对比

维度 Java Scala
if 语句,不能直接赋值 表达式,有返回值
for 循环语句 循环 + 推导式
yield 无 for yield生成新集合
switch 仅支持整数/枚举 match支持任意类型
break/continue 原生关键字 无,用Breaks类或函数式替代
try-catch 语句 表达式,有返回值
while 推荐使用 保留但不推荐
遍历集合 for-each或Iterator for推导式 + 高阶函数

Java for循环 vs Scala for推导式

// Java: 过滤偶数并平方
List<Integer> result = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
   
    if (i % 2 == 0) {
   
        result.add(i * i);
    }
}
// result: [4, 16, 36, 64, 100]
// Scala: for推导式
val result = for (i <- 1 to 10 if i % 2 == 0) yield i * i
// result: Vector(4, 16, 36, 64, 100)

// Scala: 更函数式的写法
val result = (1 to 10).filter(_ % 2 == 0).map(_ * _)
// result: Vector(4, 16, 36, 64, 100)

Java switch vs Scala match

// Java: switch
String type;
switch (day) {
   
    case "MON":
    case "TUE":
    case "WED":
    case "THU":
    case "FRI":
        type = "weekday";
        break;
    case "SAT":
    case "SUN":
        type = "weekend";
        break;
    default:
        type = "unknown";
}
// Scala: match
val type = day match {
  case "MON" | "TUE" | "WED" | "THU" | "FRI" => "weekday"
  case "SAT" | "SUN" => "weekend"
  case _ => "unknown"
}

Scala 3新语法

Scala 3对控制流语法做了简化:

// Scala 3: if表达式
if x > 0 then
  "positive"
else
  "non-positive"

// Scala 3: for循环
for i <- 1 to 5 do
  println(i)

// Scala 3: while循环
while x > 0 do
  x -= 1

// Scala 3: match表达式
val result = x match
  case 0 => "zero"
  case _ => "other"

写在最后:从命令式到表达式式的思维转变

Scala的循环与判断系统,表面上是语法差异,本质上是编程范式的差异。

Java是命令式语言:你告诉计算机"一步步做什么"——先判断条件,再执行代码块,再循环重复。Scala是表达式语言:你描述"你想要什么结果"——if返回什么值、for生成什么集合、match匹配到什么分支。

这种转变带来三个好处:

  1. 更少的可变状态:if表达式直接赋值,不需要临时变量;for推导式生成新集合,不需要手动添加元素
  2. 更强的组合性:表达式可以嵌套、可以传递、可以作为参数;语句只能顺序执行
  3. 更高的安全性:match的穷尽性检查让编译器帮你发现遗漏的分支;Try类型让异常处理显式化

这些特性不是孤立的,它们共同支撑了Scala的函数式编程范式。Spark的RDD转换链、Flink的数据流处理、Cats的函数式组合——这些框架的核心都是"描述你想要什么",而不是"告诉计算机怎么做"。

学习建议:不要试图一次记住所有语法。先从if表达式和for推导式开始,写一些小练习。当你习惯了"控制结构有返回值"的思维后,再学习match模式匹配和Try类型。循环与判断是编程的基础,但在Scala里,它们不仅是基础——它们是函数式编程的入口。

从Java的命令式循环到Scala的表达式式控制流,不是学习"另一种循环写法",而是学习一种"更声明式、更安全、更组合的思考方式"。当你习惯了用map/filter替代for循环、用match替代if-else链、用Try替代try-catch,你会发现:原来控制流可以这么优雅。

目录
相关文章
|
15天前
|
人工智能 JSON 自然语言处理
LangChain 六大使用场景:哪些值得做,哪些不值得,一张决策图说清
本文系统梳理LangChain六大核心场景(RAG、Agent、对话系统、结构化提取、内容生成、多模态),逐项解析痛点、机制、落地要点与适用边界,并附“不该用”的反向清单。强调“模型+外部世界”协同才是框架价值所在,助力AI工程化理性选型。
332 0
|
12天前
|
人工智能 中间件 Java
LangChain 到底是什么:从链式连接到 Agent 运行时,讲透 v1.0 的核心概念
LangChain 是大模型应用开发框架,非单纯API封装。v1.0聚焦Agent工程化:以`create_agent`为核心,基于LangGraph实现可持久化、可中断、可观测的智能体循环;统一模型接口、强化工具描述、分层记忆管理,并通过中间件支持人工介入、摘要压缩与隐私脱敏,推动AI从“能跑”走向“可靠生产”。(239字)
234 1
|
15天前
|
SQL 存储 分布式计算
一篇文章搞懂 Hive:从数仓定位到架构、表设计与数据倾斜治理
Hive是基于Hadoop的SQL数据仓库工具,不存数据、不算数据,仅作“胶水层”:元数据存MySQL,数据落HDFS,计算交YARN。支持内外表、分区/分桶、ORC/Parquet列存及丰富优化策略,专为海量离线分析而生,是企业级数仓十五年事实标准。(239字)
530 1
|
3天前
|
缓存 资源调度 Java
Flink TaskManager 作用:只做计算的那个进程
Flink TaskManager非“从节点”,而是唯一执行用户代码的核心进程。其OOMKilled多因内存配置失当——managed memory超限被内核强杀,日志无堆栈。根本在于厘清八段内存账、slot隔离边界与资源配比逻辑。(239字)
50 1
|
5天前
|
SQL 安全 Java
Flink SQL 之 Table API 原理及代码实现:从表达式体系到内部执行流程与实战最佳实践
本文深入解析Flink Table API——SQL的类型安全替代方案。涵盖本质定义、八大操作分类、六步执行流程、Expression表达式体系,并通过完整代码演示其优势:编译期校验、IDE友好、动态构建能力强。附六大坑点与八条最佳实践,助Java团队高效开发。
95 2
|
8天前
|
机器学习/深度学习 分布式计算 调度
HDFS 读文件流程:就近定位与故障切换机制
HDFS读取文件时,NameNode仅提供block位置(不传数据),客户端直连最近DataNode就近读取;每读完一个block自动切换至下一block的最优节点,并支持故障透明切换与校验容错——轻量定位、分布式传输、本地优先。
67 3
|
8天前
|
存储 运维 监控
Flink 快速入门之 Flink 代码编写注意点
本文详解Flink 2.3中十类高频生产隐患,按**提交期、运行期、运维期**三阶段归因:从序列化边界、类型识别、泛型推断,到状态TTL、Timer约定、迟到处理、Async I/O陷阱、uid强约束、Checkpoint落盘等,每条均配避坑写法与原理剖析,助你告别“一周一炸”,打造稳定三年的流作业。
86 1
|
9天前
|
分布式计算 分布式数据库 Spark
HDFS 写文件流程:复制管线与故障恢复机制
本文深度拆解HDFS写文件流程:`hdfs dfs -put`秒回背后,是客户端、NameNode与DataNode协同完成建文件、64KB packet切包、三节点串行管线复制、逐层ack及动态容错(如节点宕机自动剔除+异步补副本)的完整链路,揭示“写成功≠三副本齐”的设计本质。
78 1
|
4天前
|
JSON 中间件 Java
LangChain 模型调用工具:bind_tools 与 tool_calls,模型动手能力的底层协议
本文深入剖析Agent工具调用的底层协议,聚焦`bind_tools`(工具注册与Schema生成)、`tool_calls`(模型意图的标准输出)、`ToolMessage`(结果回传闭环)三大核心机制;详解`tool_choice`五种控制模式、并行调用与`tool_call_id`对应关系;对比手动循环与`create_agent`适用场景。助你穿透“黑盒”,掌握大模型应用开发的地基能力。
59 0
|
6天前
|
存储 Java 调度
Flink 2.3 任务提交与架构模型:一次提交,七道手
本文深度解析Flink 2.3任务提交全流程:厘清Client、JobManager、TaskManager与外部组件的分工;详解7步提交链路(从StreamGraph到Task部署);对比Application/Session两种部署模式差异;剖析slot资源与四层并行度(算子/环境/客户端/系统级)的配平逻辑;并总结5大高频故障及避坑指南。
133 0

热门文章

最新文章