Scala實戰專欄 (三) ——— 控制結構
scala-basic 基礎篇
@author 魯偉林
Scala基礎知識介紹,主要包括《Scala程式設計實戰》的基礎章節
GitHub地址: https://github.com/thinkingfioa/scala-in-action
本人部落格地址: http://blog.csdn.net/thinking_fioa/article/details/78265745
3. 控制結構
Scala語言和Java語言在控制結構部分很像,但是又存在有趣的差別。 第3章專案原始碼閱讀
3.1 for和foreach迴圈
迴圈語句主要用於處理三個問題: 1. 遍歷一個集合中的所有元素; 2. 對集合中的每個元素進行某個操作; 3. 利用現有的集合建立新的集合
3.1.1 for迴圈常用的遍歷
個人比較推薦如下for迴圈語法遍歷集合
def forF(array : Array[String]): Unit = {
for(entry <- array) {
println(entry.toUpperCase())
}
}
3.1.2 從for迴圈中返回值
使用for/ield組合,輸入一個集合,返回一個新的集合。在for迴圈新增yield實際上將每次迴圈的結果放入一個臨時存放區。等迴圈結束後,以集合的形式返回。
def forYield(array : Array[String]) : Array[String] = { for(entry <- array) yield { entry.toUpperCase() } }
3.1.3 for迴圈計數器
訪問迴圈內的計數器,即通過一個計數器訪問陣列元素,使用array.indices
def forCount(array : Array[String]): Unit = {
for(i <- array.indices) {
println(s"$i is ${array(i)}")
}
}
3.1.4 遍歷一個Map
遍歷Map中的鍵值對,下面的Map迴圈方法最簡潔和可讀
def forMap(namesMap : Map[String, Int]) : Unit = { for((k,v) <- namesMap) { println(s"key is $k, value is $v") } }
3.1.5 另一種遍歷集合方式
使用foreach, map, flatmap, collect, reduce等方法
- array.foreach(println) ----- 遍歷array陣列,可以使用自定義方法替換println方法
- array.foreach( e => println(e.toUpperCase)) ----- 使用匿名函式的語法
- array.foreach{ e => | val s = e.toUpperCase | println(s)|} ----- 實現多行函式
3.1.6 for迴圈式是如何被解釋的
提供簡化版的規則,幫助理解for迴圈執行過程
- 遍歷集合的一個簡單的for迴圈被解釋為foreach方法呼叫
- 帶有衛語句的for迴圈(3.3節)被解釋為一個foreach呼叫後在集合上呼叫withFilter方法的序列
- 帶有yield表示式的for迴圈被解釋為集合上呼叫map方法
- 帶有yield表示式和衛語句被解釋為在集合上呼叫withFilter方法,緊接著一個map方法
3.2 在for迴圈中使用多個計數器
建立有多個計數器的迴圈,如遍歷多維陣列的情況。推薦使用大括號的程式碼風格,如下:
程式碼
def forMoreCount() : Unit = {
for {
i <- 1 to 3
j <- 1 to 5
k <- 1 to 10
} {
println("next: ")
println(s"i : $i, j : $j, k : $k")
}
}
3.3 在for迴圈中嵌入if語句(衛語句)
for迴圈中新增一個或者多個條件語句,典型的應用場景就是將一個元素從集合中過濾掉。 推薦使用大括號編碼風格
程式碼
def forIf() : Unit = {
for {
i <- 1 to 10
if i> 3
if i<= 8
if i % 2 == 0
} println(i)
}
3.4 建立for表示式( for/yield組合 )
對一個已有的集合中的每個元素應用某個演算法,從而生成新的集合。在for迴圈中使用yield語句的方式通常被叫作for推導
程式碼
def forYieldMoreLine(array : Array[String]) : Array[Int] = {
for( e <- array) yield {
val eUpper = e.toUpperCase()
eUpper.length
}
}
理解for/yield表示式
- 開始執行時,for/yield迴圈立刻建立一個新的空集合(Bucket),型別與輸入的集合相同
- for迴圈每次遍歷,都會在輸入集合中的每個元素基礎上建立新的元素,加入到Bucket中
- 迴圈結束後,Bucket中的所有內容都被返回
3.5 實現break和continue
遺憾的是Scala沒有break或者continue關鍵字,但是scala.util.control.Breaks提供了類似的功能。相對於Java來說,Scala的break和continue較為複雜化。個人不喜歡這種風格
程式碼
import util.control.Breaks._
object Ctrl3P5 {
def forBreak(array : Array[Int]) : Unit = {
breakable {
for(i <- array) {
if(i== 2) {
break()
} else {
print(i+", ")
}
}
}
}
def forContinue(array : Array[Int]) : Unit = {
for(i <- array) {
breakable {
if(i== 2) {
break()
} else {
print(i +", ")
}
}
}
}
}
3.6 像三元運算子一樣使用if
Java提供條件運算子 ? : 稱為三元運算子,但是Scala沒有提供。在scala中只能使用if/else語句。eg x >=0 ? "yes": "no" 等價於 if(x>=0) "yes" else "no"
3.7 像swtich語句一樣使用匹配表示式
Java中基於整數的switch語句,Scala也支援使用@switch註解來滿足switch語句,同時@switch註解效能會更優越
程式碼
def switchTypo(i : Int) : String = {
(i : @switch) match {
case 1 => "One"
case 2 => "Two"
case _ => "Other"
}
}
匹配表示式不侷限於整數,它是非常靈活的,如下型別的匹配也是支援的
程式碼
def switchType(x : Any) : String = {
(x : @switch) match {
case s : String => "One"
case i : Int => "Two"
case _ => "Other"
}
}
3.7.1 處理預設情況
處理預設情況的兩種情況:
- 不關心預設匹配的值,使用萬用字元去捕獲 ----- case _ => println("default")
- 關係預設匹配的值,指定一個變數,然後在表示式右側使用該變數 ----- case default => println(default)
- 如果不處理預設情況,可能會產生MatchError錯誤。所以建議要處理default case
3.8 一條case語句匹配多個條件
多個匹配條件需要執行相同的業務邏輯時,使用一條case語句匹配多個條件
程式碼
def moreCase(x : Int) : Unit = {
(x : @switch) match {
case 1 | 3 | 5 | 7 | 9 => println("odd")
case 2 | 4 | 6 | 8 | 10 => println("even")
}
}
3.9 將匹配表示式的結果賦值給變數
將一個匹配表示式返回值賦值給一個變數,或將匹配表示式作為方法的主體
def isTrue(a : Any) = a match{
case 0 | "" => false
case _ => true
}
3.12 在匹配表示式中使用Case類
在一個匹配表示式中匹配不同的case類(或者case物件)。如下面的程式碼,Dog和Cat case類以及Woodpecker case物件都是Animal trait的子型別
程式碼
trait Animal
case class Dog(name : String) extends Animal
case class Cat(name : String) extends Animal
case object Woodpecker extends Animal
object Ctrl3P11 {
def main(args: Array[String]): Unit = {
println(determineType(Dog("ppp")))
println(determineType(Cat("fioa")))
println(determineType(Woodpecker))
}
def determineType(x : Animal) : String = x match {
case Dog(moniker) => "Got a Dog, name = "+moniker
case _ : Cat => "Got a Cat"
case Woodpecker => "That was a Wood"
case _ => "default"
}
}
3.13 給Case語句新增if表示式(衛語句)
給匹配的表示式內的case語句新增合適的邏輯,幫助增強case語句的約束條件
object Ctrl3P13 {
def main(args: Array[String]): Unit = {
caseIfNum(1)
caseIfNum(2)
}
def caseIfNum(x : Int) : Unit = {
(x : @switch) match {
case m if m==1 => println("one, a lonely number")
case n if n==2 || n==3 => println(n)
case _ => println("default")
}
}
}
3.14 使用匹配表示式替換isInstanceOf
判斷物件是否匹配一個型別,可以通過使用isInstanceOf來判斷,eg: if(x isInstanceOf[Class]) 。但當需求負責情況寫,寫一個程式碼塊去匹配一種型別或者多個不同的型別的可讀性更好。
程式碼
trait SentientBeing
trait Animal2 extends SentientBeing
case class Pig(name : String) extends Animal2
case class Person(name:String, age : Int) extends SentientBeing
object Ctrl3P14 {
def main(args: Array[String]): Unit = {
casePrintInfo(Person("luweilin", 24))
casePrintInfo(Pig("ppp"))
if(Pig("ppp").isInstanceOf[SentientBeing]) {
println("true")
}
}
def casePrintInfo(x : SentientBeing) : Unit = x match {
case Person(name, age) => println(s"$name is $name, age is $age")
case Pig(name) => println(s"pig name is $name")
case _ => println("default")
}
}
3.15 在匹配的表示式中使用List
List資料結構和其他的集合資料結構略有不同。列表由單元開始,Nil元素結尾。如果下遞迴列印內容
object Ctrl3P15 {
def main(args: Array[String]): Unit = {
val x = List(1,2,3,4)
println(caseList(x))
}
def caseList(x : List[Int]) : String = x match {
case s :: rest => s +", " + caseList(rest)
case Nil => ""
}
}
3.16 用try/catch匹配一個或多個異常
在try/catch塊捕捉一個或者更多的異常
def moreException(fileName : String) : Unit = {
try {
// read config File
} catch {
case e : FileNotFoundException => println("Colud find that file.")
case e : IOException => println("IOException trying to read that file")
}
}
注意: Scala中沒有受檢異常,因此不需要指定丟擲異常的方法。如果需要宣告方法丟擲的異常,或者需要和Java互動,在定義方法時新增@throws註解
@throws(classOf[NumberFormatException])
def throwException(fileName : String) : Unit = {
try {
// read config File
} catch {
case e : NumberFormatException => throw e
}
}
3.17 在try/catch/finally塊中使用變數前定義變數問題
在try程式碼中使用一個變數,並在finally程式碼塊中訪問該物件,如呼叫物件的close方法。一般情況下,在try/catch塊前宣告欄位為Option,然後在try子句中建立一個Some物件,finally中執行關閉。在Scala中建議不要使用null
程式碼:
def tryCatchFinally(fileName : String) : Unit = {
var in = None : Option[FileInputStream]
try {
// open file Name
} catch {
case cause: IOException => cause.printStackTrace()
} finally {
if(in.isDefined) {
in.get.close()
}
}
}
3.18 建立自定義控制結構
Scala語言的創造者有意決定通過Scala類庫去實現功能而不是去建立一些關鍵字。典型的例子就是: break和continue關鍵字。開發者可以自定義控制結構,建立可用的DSL去給他人所用
3.18.1 建立一個類似於while迴圈控制結構
object Ctrl3P18 {
def main(args: Array[String]): Unit = {
var i =0
whilst(i<5) {
println(s"index: $i")
i += 1
}
}
@tailrec
def whilst(testCondition : => Boolean) (codeBlock : => Unit) {
if(testCondition) {
codeBlock
whilst(testCondition)(codeBlock)
}
}
}
解釋:
自定義函式名:whilst,接受兩個引數列表,第一個是引數列表測試條件,一個表示使用者想要執行的程式碼塊