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
循环遍历映射时,每个键/值对都绑定到变量 abbrev
和 fullName
,它们位于元组中
(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
。表达式的运作方式如下
for
表达式开始遍历范围(10, 11, 12)
中的值。它首先处理值10
,将其乘以2
,然后生成该结果,即值20
。- 接下来,它处理
11
——范围中的第二个值。它将其乘以2
,然后生成值22
。您可以将这些生成的值视为累积在临时存放处。 - 最后,循环从该范围获取数字
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
表达式会生成结果,因此它可用作返回有用值的方法的主体。此方法返回给定整数列表中介于 3
和 10
之间的所有值
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
与所示情况进行测试。如果它介于 0
和 6
之间,则 day
将绑定到表示该星期几的字符串。否则,它将匹配由字符 _
表示的万能情况,而 day
将绑定到字符串 "invalid day"
。
由于情况按书写顺序进行考虑,并且使用了第一个匹配的情况,因此必须将匹配任何值的默认情况放在最后。万能情况之后的任何情况都将被警告为无法到达的情况。
在编写像这样的简单
match
表达式时,建议对变量i
使用@switch
注解。如果无法将 switch 编译为tableswitch
或lookupswitch
(它们对于性能更好),则此注解会提供编译时警告。
使用默认值
当你需要在 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
保护
你还可以使用 case
s 中的 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.*
类来读取文件并且不捕获其异常,则尝试打开和读取文件可能会导致 FileNotFoundException
和 IOException
,并且这两个异常都捕获在该示例的 catch
块中。