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
與 Map
您也可以將 for
迴圈與 Map
搭配使用。例如,給定此 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
迴圈反覆執行 map 時,每個金鑰/值對會繫結到變數 abbrev
和 fullName
,這些變數在一個組中
(abbrev, fullName) <- states
當迴圈執行時,變數 abbrev
會指定給 map 中目前的金鑰,而變數 fullName
會指定給目前的 map 值。
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
中使用防護。在此範例中,第二個和第三個 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
區塊中被捕捉。