Scala 3 — Book

控制结构

语言

Scala 具有编程语言中常见的控制结构,包括

  • if/then/else
  • for 循环
  • while 循环
  • try/catch/finally

它还有另外两种强大的结构,根据你的编程背景,你可能之前没有见过

  • for 表达式(也称为for 理解
  • match 表达式

以下部分将演示所有这些内容。

if/then/else 结构

单行 Scala if 语句如下所示

if (x == 1) println(x)
if x == 1 then println(x)

当您需要在 if 等式比较后运行多行代码时,请使用此语法

if (x == 1) {
  println("x is 1, as you can see:")
  println(x)
}
if x == 1 then
  println("x is 1, as you can see:")
  println(x)

if/else 语法如下所示

if (x == 1) {
  println("x is 1, as you can see:")
  println(x)
} else {
  println("x was not 1")
}
if x == 1 then
  println("x is 1, as you can see:")
  println(x)
else
  println("x was not 1")

这是 if/else if/else 语法

if (x < 0)
  println("negative")
else if (x == 0)
  println("zero")
else
  println("positive")
if x < 0 then
  println("negative")
else if x == 0 then
  println("zero")
else
  println("positive")

end if 语句

  这是 Scala 3 中的新增功能,在 Scala 2 中不受支持。

如果您愿意,可以选择在每个表达式的末尾包含一个 end if 语句

if x == 1 then
  println("x is 1, as you can see:")
  println(x)
end if

if/else 表达式始终返回结果

请注意,if/else 比较形成表达式,这意味着它们返回您可以分配给变量的值。因此,不需要特殊的三元运算符

val minValue = if (a < b) a else b
val minValue = if a < b then a else b

由于它们返回一个值,因此您可以将 if/else 表达式用作方法的主体

def compare(a: Int, b: Int): Int =
  if (a < b)
    -1
  else if (a == b)
    0
  else
    1
def compare(a: Int, b: Int): Int =
  if a < b then
    -1
  else if a == b then
    0
  else
    1

旁注:面向表达式的编程

作为对一般编程的简要说明,当您编写的每个表达式都返回一个值时,该样式被称为面向表达式的编程或 EOP。例如,这是一个表达式

val minValue = if (a < b) a else b
val minValue = if a < b then a else b

相反,不返回值的行代码称为语句,它们用于其副作用。例如,这些代码行不返回值,因此它们用于其副作用

if (a == b) action()
println("Hello")
if a == b then action()
println("Hello")

第一个示例在 a 等于 b 时将 action 方法作为副作用运行。第二个示例用于将字符串打印到 STDOUT 的副作用。随着您对 Scala 的深入了解,您会发现自己编写越来越多的表达式和越来越少的语句

for 循环

在最简单的用法中,Scala for 循环可用于遍历集合中的元素。例如,给定一个整数序列,你可以遍历其元素并像这样打印其值

val ints = Seq(1, 2, 3)
for (i <- ints) println(i)
val ints = Seq(1, 2, 3)
for i <- ints do println(i)

代码 i <- ints 称为生成器。在任何生成器 p <- e 中,表达式 e 可以生成零个或多个绑定到模式 p 的绑定。

这是在 Scala REPL 中的结果

scala> val ints = Seq(1,2,3)
ints: Seq[Int] = List(1, 2, 3)

scala> for (i <- ints) println(i)
1
2
3
scala> val ints = Seq(1,2,3)
ints: Seq[Int] = List(1, 2, 3)

scala> for i <- ints do println(i)
1
2
3

for 生成器后需要一个多行代码块时,请使用以下语法

for (i <- ints) {
  val x = i * 2
  println(s"i = $i, x = $x")
}
for i <- ints
do
  val x = i * 2
  println(s"i = $i, x = $x")

多个生成器

for 循环可以有多个生成器,如下例所示

for {
  i <- 1 to 2
  j <- 'a' to 'b'
  k <- 1 to 10 by 5
} {
  println(s"i = $i, j = $j, k = $k")
}
for
  i <- 1 to 2
  j <- 'a' to 'b'
  k <- 1 to 10 by 5
do
  println(s"i = $i, j = $j, k = $k")

该表达式打印此输出

i = 1, j = a, k = 1
i = 1, j = a, k = 6
i = 1, j = b, k = 1
i = 1, j = b, k = 6
i = 2, j = a, k = 1
i = 2, j = a, k = 6
i = 2, j = b, k = 1
i = 2, j = b, k = 6

保护

for 循环还可以包含 if 语句,称为保护

for {
  i <- 1 to 5
  if i % 2 == 0
} {
  println(i)
}
for
  i <- 1 to 5
  if i % 2 == 0
do
  println(i)

该循环的输出为

2
4

for 循环可以根据需要包含任意多个保护。此示例显示了一种打印数字 4 的方法

for {
  i <- 1 to 10
  if i > 3
  if i < 6
  if i % 2 == 0
} {
  println(i)
}
for
  i <- 1 to 10
  if i > 3
  if i < 6
  if i % 2 == 0
do
  println(i)

使用 for 与 Maps

你还可以对 Map 使用 for 循环。例如,给定此 Map 的州缩写及其全名

val states = Map(
  "AK" -> "Alaska",
  "AL" -> "Alabama", 
  "AR" -> "Arizona"
)

你可以像这样使用 for 打印键和值

for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName")
for (abbrev, fullName) <- states do println(s"$abbrev: $fullName")

以下是 REPL 中的样子

scala> for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName")
AK: Alaska
AL: Alabama
AR: Arizona
scala> for (abbrev, fullName) <- states do println(s"$abbrev: $fullName")
AK: Alaska
AL: Alabama
AR: Arizona

for 循环遍历映射时,每个键/值对都绑定到变量 abbrevfullName,它们位于元组中

(abbrev, fullName) <- states

随着循环的运行,变量 abbrev 被分配到映射中的当前,变量 fullName 被分配到当前映射

for 表达式

在先前的 for 循环示例中,这些循环全部用于副作用,具体来说是使用 println 将这些值打印到 STDOUT。

了解到您还可以创建返回值的 for 表达式非常重要。您可以通过添加 yield 关键字和要返回的表达式来创建 for 表达式,如下所示

val list =
  for (i <- 10 to 12)
  yield i * 2

// list: IndexedSeq[Int] = Vector(20, 22, 24)
val list =
  for i <- 10 to 12
  yield i * 2

// list: IndexedSeq[Int] = Vector(20, 22, 24)

在该 for 表达式运行后,变量 list 是包含所示值的 Vector。表达式的运作方式如下

  1. for 表达式开始遍历范围 (10, 11, 12) 中的值。它首先处理值 10,将其乘以 2,然后生成该结果,即值 20
  2. 接下来,它处理 11——范围中的第二个值。它将其乘以 2,然后生成值 22。您可以将这些生成的值视为累积在临时存放处。
  3. 最后,循环从该范围获取数字 12,将其乘以 2,生成数字 24。循环在此处完成并生成最终结果,即 Vector(20, 22, 24)

虽然本节的目的是演示 for 表达式,但了解所示的 for 表达式等效于此 map 方法调用会有所帮助

val list = (10 to 12).map(i => i * 2)

for 表达式可在您需要遍历集合中的所有元素并将算法应用于这些元素以创建新列表的任何时候使用。

以下示例展示了如何在 yield 后使用代码块

val names = List("_olivia", "_walter", "_peter")

val capNames = for (name <- names) yield { 
  val nameWithoutUnderscore = name.drop(1)
  val capName = nameWithoutUnderscore.capitalize
  capName
}

// capNames: List[String] = List(Olivia, Walter, Peter)
val names = List("_olivia", "_walter", "_peter")

val capNames = for name <- names yield
  val nameWithoutUnderscore = name.drop(1)
  val capName = nameWithoutUnderscore.capitalize
  capName

// capNames: List[String] = List(Olivia, Walter, Peter)

使用 for 表达式作为方法的主体

由于 for 表达式会生成结果,因此它可用作返回有用值的方法的主体。此方法返回给定整数列表中介于 310 之间的所有值

def between3and10(xs: List[Int]): List[Int] =
  for {
    x <- xs
    if x >= 3
    if x <= 10
  } yield x

between3and10(List(1, 3, 7, 11))   // : List[Int] = List(3, 7)
def between3and10(xs: List[Int]): List[Int] =
  for
    x <- xs
    if x >= 3
    if x <= 10
  yield x

between3and10(List(1, 3, 7, 11))   // : List[Int] = List(3, 7)

while 循环

Scala while 循环语法如下所示

var i = 0

while (i < 3) {
  println(i)
  i += 1
}
var i = 0

while i < 3 do
  println(i)
  i += 1

match 表达式

模式匹配是函数式编程语言的一个主要特性,而 Scala 包含一个功能强大的 match 表达式。

在最简单的情况下,你可以使用 match 表达式,就像 Java switch 语句一样,根据整数值匹配情况。请注意,这实际上是一个表达式,因为它会计算出一个结果

// `i` is an integer
val day = i match {
  case 0 => "Sunday"
  case 1 => "Monday"
  case 2 => "Tuesday"
  case 3 => "Wednesday"
  case 4 => "Thursday"
  case 5 => "Friday"
  case 6 => "Saturday"
  case _ => "invalid day"   // the default, catch-all
}
// `i` is an integer
val day = i match
  case 0 => "Sunday"
  case 1 => "Monday"
  case 2 => "Tuesday"
  case 3 => "Wednesday"
  case 4 => "Thursday"
  case 5 => "Friday"
  case 6 => "Saturday"
  case _ => "invalid day"   // the default, catch-all

在此示例中,变量 i 与所示情况进行测试。如果它介于 06 之间,则 day 将绑定到表示该星期几的字符串。否则,它将匹配由字符 _ 表示的万能情况,而 day 将绑定到字符串 "invalid day"

由于情况按书写顺序进行考虑,并且使用了第一个匹配的情况,因此必须将匹配任何值的默认情况放在最后。万能情况之后的任何情况都将被警告为无法到达的情况。

在编写像这样的简单 match 表达式时,建议对变量 i 使用 @switch 注解。如果无法将 switch 编译为 tableswitchlookupswitch(它们对于性能更好),则此注解会提供编译时警告。

使用默认值

当你需要在 match 表达式中访问万能的默认值时,只需在 case 语句的左侧提供一个变量名,而不是 _,然后根据需要在语句的右侧使用该变量名

i match {
  case 0 => println("1")
  case 1 => println("2")
  case what => println(s"You gave me: $what")
}
i match
  case 0 => println("1")
  case 1 => println("2")
  case what => println(s"You gave me: $what")

在模式中使用的名称必须以小写字母开头。以大写字母开头的名称不会引入变量,而是匹配范围内的值

val N = 42
i match {
  case 0 => println("1")
  case 1 => println("2")
  case N => println("42")
  case n => println(s"You gave me: $n" )
}
val N = 42
i match
  case 0 => println("1")
  case 1 => println("2")
  case N => println("42")
  case n => println(s"You gave me: $n" )

如果 i 等于 42,则 case N 将匹配,并且它将打印字符串 "42"。它不会达到默认情况。

在一行上处理多个可能的匹配项

如上所述,match 表达式具有许多功能。此示例展示了如何在每个 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")
}
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 保护

你还可以使用 cases 中的 match 表达式的保护。在此示例中,第二个和第三个 case 都使用保护来匹配多个整数值

i 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")
}
i 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")

以下是如何匹配给定值与数字范围的另一个示例

i match {
  case a if 0 to 9 contains a => println(s"0-9 range: $a")
  case b if 10 to 19 contains b => println(s"10-19 range: $b")
  case c if 20 to 29 contains c => println(s"20-29 range: $c")
  case _ => println("Hmmm...")
}
i match
  case a if 0 to 9 contains a => println(s"0-9 range: $a")
  case b if 10 to 19 contains b => println(s"10-19 range: $b")
  case c if 20 to 29 contains c => println(s"20-29 range: $c")
  case _ => println("Hmmm...")

案例类和匹配表达式

你还可以从 case 类中提取字段,以及具有正确编写的 apply/unapply 方法的类,并在保护条件中使用它们。以下是一个使用简单 Person 案例类的示例

case class Person(name: String)

def speak(p: Person) = p match {
  case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo")
  case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!")
  case _ => println("Watch the Flintstones!")
}

speak(Person("Fred"))      // "Fred says, Yubba dubba doo"
speak(Person("Bam Bam"))   // "Bam Bam says, Bam bam!"
case class Person(name: String)

def speak(p: Person) = p match
  case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo")
  case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!")
  case _ => println("Watch the Flintstones!")

speak(Person("Fred"))      // "Fred says, Yubba dubba doo"
speak(Person("Bam Bam"))   // "Bam Bam says, Bam bam!"

使用 match 表达式作为方法的主体

因为 match 表达式返回一个值,所以它们可以用作方法的主体。此方法将 Matchable 值作为输入参数,并基于 match 表达式的结果返回 Boolean

def isTruthy(a: Matchable) = a match {
  case 0 | "" | false => false
  case _              => true
}
def isTruthy(a: Matchable) = a match
  case 0 | "" | false => false
  case _              => true

输入参数 a 被定义为 Matchable 类型——这是模式匹配可执行的所有 Scala 类型的根类型。该方法通过匹配输入来实现,提供两种情况:第一种情况检查给定的值是否为整数 0、空字符串或 false,并在此情况下返回 false。在默认情况下,我们为任何其他值返回 true。这些示例展示了此方法的工作原理

isTruthy(0)      // false
isTruthy(false)  // false
isTruthy("")     // false
isTruthy(1)      // true
isTruthy(" ")    // true
isTruthy(2F)     // true

使用 match 表达式作为方法的主体是一种非常常见的用法。

匹配表达式支持许多不同类型的模式

有许多不同形式的模式可用于编写 match 表达式。示例包括

  • 常量模式(例如 case 3 =>
  • 序列模式(例如 case List(els : _*) =>
  • 元组模式(例如 case (x, y) =>
  • 构造器模式(例如 case Person(first, last) =>
  • 类型测试模式(例如 case p: Person =>

所有这些类型的模式都显示在以下 pattern 方法中,该方法采用类型为 Matchable 的输入参数并返回 String

def pattern(x: Matchable): String = x match {

  // constant patterns
  case 0 => "zero"
  case true => "true"
  case "hello" => "you said 'hello'"
  case Nil => "an empty List"

  // sequence patterns
  case List(0, _, _) => "a 3-element list with 0 as the first element"
  case List(1, _*) => "list, starts with 1, has any number of elements"
  case Vector(1, _*) => "vector, starts w/ 1, has any number of elements"

  // tuple patterns
  case (a, b) => s"got $a and $b"
  case (a, b, c) => s"got $a, $b, and $c"

  // constructor patterns
  case Person(first, "Alexander") => s"Alexander, first name = $first"
  case Dog("Zeus") => "found a dog named Zeus"

  // type test patterns
  case s: String => s"got a string: $s"
  case i: Int => s"got an int: $i"
  case f: Float => s"got a float: $f"
  case a: Array[Int] => s"array of int: ${a.mkString(",")}"
  case as: Array[String] => s"string array: ${as.mkString(",")}"
  case d: Dog => s"dog: ${d.name}"
  case list: List[?] => s"got a List: $list"
  case m: Map[?, ?] => m.toString

  // the default wildcard pattern
  case _ => "Unknown"
}
def pattern(x: Matchable): String = x match

  // constant patterns
  case 0 => "zero"
  case true => "true"
  case "hello" => "you said 'hello'"
  case Nil => "an empty List"

  // sequence patterns
  case List(0, _, _) => "a 3-element list with 0 as the first element"
  case List(1, _*) => "list, starts with 1, has any number of elements"
  case Vector(1, _*) => "vector, starts w/ 1, has any number of elements"

  // tuple patterns
  case (a, b) => s"got $a and $b"
  case (a, b, c) => s"got $a, $b, and $c"

  // constructor patterns
  case Person(first, "Alexander") => s"Alexander, first name = $first"
  case Dog("Zeus") => "found a dog named Zeus"

  // type test patterns
  case s: String => s"got a string: $s"
  case i: Int => s"got an int: $i"
  case f: Float => s"got a float: $f"
  case a: Array[Int] => s"array of int: ${a.mkString(",")}"
  case as: Array[String] => s"string array: ${as.mkString(",")}"
  case d: Dog => s"dog: ${d.name}"
  case list: List[?] => s"got a List: $list"
  case m: Map[?, ?] => m.toString

  // the default wildcard pattern
  case _ => "Unknown"

try/catch/finally

与 Java 类似,Scala 具有 try/catch/finally 结构,可用于捕获和管理异常。为了保持一致性,Scala 使用与 match 表达式相同的语法,并支持对可能发生的各种异常进行模式匹配。

在以下示例中,openAndReadAFile 是一个方法,它执行其名称所暗示的操作:它打开一个文件并读取其中的文本,将结果分配给可变变量 text

var text = ""
try {
  text = openAndReadAFile(filename)
} catch {
  case fnf: FileNotFoundException => fnf.printStackTrace()
  case ioe: IOException => ioe.printStackTrace()
} finally {
  // close your resources here
  println("Came to the 'finally' clause.")
}
var text = ""
try
  text = openAndReadAFile(filename)
catch
  case fnf: FileNotFoundException => fnf.printStackTrace()
  case ioe: IOException => ioe.printStackTrace()
finally
  // close your resources here
  println("Came to the 'finally' clause.")

假设 openAndReadAFile 方法使用 Java java.io.* 类来读取文件并且不捕获其异常,则尝试打开和读取文件可能会导致 FileNotFoundExceptionIOException,并且这两个异常都捕获在该示例的 catch 块中。

此页面的贡献者