1、前言java
前面學習了Scala的Numbers,接着學習Scala的Control Structures(控制結構)。算法
2、Control Structures數組
Scala中的控制結構與Java中的頗爲相似,但也有所不一樣,例如,if/then/else控制結構與Java的相似,可是其能夠返回值,雖然Java中有三元運算符的特殊語法,可是在Scala中使用if就能夠達到一樣的效果。 數據結構
val x = if (a) y else z
一樣,Scala的try/catch/finally 結構與Java的也相似,可是Scala在catch中時使用的模式匹配。閉包
當使用for循環時,狀況會變得更有趣,for循環的基本用法在Scala和Java中都相同,可是在Scala能夠添加守衛。例如,可使用兩個for循環來讀取文件中的每一行,而且對每一行的每一個字符進行操做。 dom
for (line <- source.getLines) { for { char <- line if char.isLetter } // char algorithm here ... }
在Scala中,還可使用更精煉的語句實現上述功能 ide
for { line <- source.getLines char <- line if char.isLetter } // char algorithm here ...
在Scala中,使用for循環可讓你可以很輕易的將一個集合轉化成另一個集合函數
在最基礎的使用中,Scala的match表達式與Java的Switch語句相似,你能夠匹配任何對象,而且從對象中解析信息。oop
2.1 for和foreach循環性能
1. 問題描述
你須要遍歷集合的元素,或者操做集合中的元素,或者在已有集合上建立新的集合
2. 解決方案
有不少方式能夠遍歷集合,如for、while循環,還有集合的foreach、map、flatMap方法等,首先看看for循環
當你的算法有多行時,也可使用for循環,以下所示
上述示例中僅僅只是遍歷了集合中的元素,而且沒有返回值,如新的Array,當你想要從已有集合中建立新的集合時,可使用for/yield
for/yield結構返回了一個新的Array。
當在for循環中須要訪問計數器時,可使用以下方法
Scala集合也提供了zipWithIndex方法來建立循環計數器
下面示例展現瞭如何經過Range來執行循環三次
以下示例展現瞭如何使用守衛(在for循環中使用if語句)
可使用以下精煉方式遍歷Map的鍵集合和值集合
3. 討論
當你在集合上使用for/yield時,會獲得一個新的集合,而僅僅使用for時,則不會產生新的集合,僅僅只是利用for的反作用對集合元素進行操做。for循環可能不是遍歷集合最好的方式,使用foreach、map、flatMap、collect、reduce等方法可能會更高效的解決問題。
你能夠調用foreach方法來遍歷每一個集合元素
可使用匿名函數對元素進行操做
for循環如何被翻譯
· for循環會被編譯器翻譯成foreach方法
· 有守衛的for循環會被翻譯成withFilter方法(在foreach以後)
· 有yield的for循環會被翻譯成map方法
· 有yield和守衛的for循環會被翻譯成withFilter(在map以後)
爲了驗證for循環的翻譯過程,新建Main.scala文件,源代碼以下
class Main { for (i <- 1 to 10) println(i) }
使用scalac -Xprint:parse Main.scala命令進行編譯
能夠看到for循環被翻譯成了foreach方法
其餘的帶守衛、yield的for循環也能夠採用上述方法進行查看,再也不累贅
2.2 使用多計數器的for循環
1. 問題描述
你想要建立擁有多個計數器的循環,如當遍歷一個多維數組
2. 解決方案
你可使用以下方法建立多個計數器
一種更好的方式是使用中括號
同理,也可使用三個計數器
當遍歷多位數組時,該方法頗有效
3. 討論
在循環中,使用<-符號生成Range被稱爲生成器,能夠在一個循環中使用多個生成器
2.3 使用內嵌語句的for循環
1. 問題描述
你想要在for循環中添加一個或多個條件子句,一般在篩選集合中的某些元素
2. 解決方案
能夠在生成器後面添加if語句
或者使用中括號
上述的if語句被稱爲過濾器或守衛,下面示例添加了多個守衛
3. 討論
在for循環中使用守衛能夠是代碼精煉而且提升可讀性,固然也可使用傳統的方法
2.4 建立for/yield組合
1. 問題描述
你想要經過對原始集合中的每一個元素應用某種算法(一個或多個守衛)來從現有集合建立新的集合
2. 解決方案
使用yield和算法來從現有集合生成新的集合
若是算法有多行,能夠採用以下形式
一般狀況下,使用yield新生的集合的類型和原始集合的類型相同,如原始集合時ArrayBuffer類型,則新生的集合也是ArrayBuffer類型
3. 討論
當在for循環中使用yield時,能夠將其看作以下
· 開始運行時,for/yield會新建一個空的集合,類型和原始集合類型相同
· 在for循環的每次迭代中,從輸入集合的當前元素建立新的輸出元素,放置在新的集合中
· 當for循環結束時,返回新的集合中的全部內容
沒有守衛的for/yield語句就至關於調用集合的map方法
2.5 實現break和continue
1. 問題描述
你須要使用break和continue,可是在Scala中並無break和continue關鍵字
2. 解決方案
scala.util.control.Breaks提供了break功能
package com.hust.grid.leesf.breakcontinue /** * Created by LEESF on 2017/1/18. */ import util.control.Breaks._ object BreakAndContinueDemo extends App{ println("\n=== BREAK EXAMPLE ===") breakable { for (i <- 1 to 10) { println(i) if (i > 4) break // break out of the for loop } } println("\n=== CONTINUE EXAMPLE ===") val searchMe = "peter piper picked a peck of pickled peppers" var numPs = 0 for (i <- 0 until searchMe.length) { breakable { if (searchMe.charAt(i) != 'p') { break // break out of the 'breakable', continue the outside loop } else { numPs += 1 } } } println("Found " + numPs + " p's in the string.") }
結果
=== BREAK EXAMPLE ===
1
2
3
4
5
=== CONTINUE EXAMPLE ===
Found 9 p's in the string.
值得注意的是break和breakable都不是Scala的關鍵字,它們是scala.util.control.Breaks包下的方法
private val breakException = new BreakControl def break(): Nothing = { throw breakException } def breakable(op: => Unit) { try { op } catch { case ex: BreakControl => if (ex ne breakException) throw ex } }
能夠看到在調用break方法時,會拋出一個異常,此時breakable會捕捉到此異常,利用這種方式實現了break和continue的功能
3. 討論
若是你不喜歡使用break和continue時,可使用以下方法,如可使用帶有守衛的for循環
var barrelIsFull = false for (monkey <- monkeyCollection if !barrelIsFull) { addMonkeyToBarrel(monkey) barrelIsFull = checkIfBarrelIsFull }
或者使用return直接返回
def sumToMax(arr: Array[Int], limit: Int): Int = { var sum = 0 for (i <- arr) { sum += i if (sum > limit) return limit } sum } val a = Array.range(0,10) println(sumToMax(a, 10))
2.6 使用相似三元運算符的if表達式
1. 問題描述
你須要使用if表達式來像三元運算符那樣簡潔的解決問題
2. 解決方案
在Scala中沒有三元運算法,可是可使用if/else表達式
3. 討論
在Java中可使用?:三元運算符,在Scala可使用if/else表達式
2.7 使用相似switch語句的match表達式
1. 問題描述
你想要建立向Java那樣簡單的switch語句,如星期幾,哪一月
2. 解決方案
使用Scala的match表達式
// i is an integer i match { case 1 => println("January") case 2 => println("February") case 3 => println("March") case 4 => println("April") case 5 => println("May") case 6 => println("June") case 7 => println("July") case 8 => println("August") case 9 => println("September") case 10 => println("October") case 11 => println("November") case 12 => println("December") // catch the default with a variable so you can print it case whoa => println("Unexpected case: " + whoa.toString) }
還能夠直接利用match表達式的返回值
val month = i match { case 1 => "January" case 2 => "February" case 3 => "March" case 4 => "April" case 5 => "May" case 6 => "June" case 7 => "July" case 8 => "August" case 9 => "September" case 10 => "October" case 11 => "November" case 12 => "December" case _ => "Invalid month" // the default, catch-all }
當寫match表達式時,推薦使用@switch註解,當switch不能編譯成一個tableswitch或lookupswitch,該註解會提供編譯時警告,將switch編譯成tableswitch或lookupswitch會有更高的性能,由於結果是在分支表而不是一個決策樹裏,當給表達式賦值時,它能夠直接跳轉到結果,而不是遍歷決策樹
import scala.annotation.switch class SwitchDemo { val i = 1 val x = (i: @switch) match { case 1 => "One" case 2 => "Two" case _ => "Other" } }
先使用scalac SwitchDemo命令進行編譯,而後使用javap SwitchDemo命令反編譯
能夠看到生成了tableswitch表,當修改代碼以下時
import scala.annotation.switch class SwitchDemo { val i = 1 val Two = 2 // added val x = (i: @switch) match { case 1 => "One" case Two => "Two" // replaced the '2' case _ => "Other" } }
一樣使用scalac SwitchDemo命令進行編譯
這代表match表達式沒法生成tableswitch或lookupswitch,當使用javap命令反彙編時,也找不到tableswitch
3. 討論
match表達式很是靈活,能夠匹配不一樣的類型
def getClassAsString(x: Any): String = x match { case s: String => s + " is a String" case i: Int => "Int" case f: Float => "Float" case l: List[_] => "List" case p: Person => "Person" case _ => "Unknown" }
可使用case _通配符來匹配以前的全部case沒法匹配的狀況,或者使用case default進行賦值
case default => println(default)
2.8 用一個case語句匹配多個條件
1. 問題描述
當幾個匹配條件要求執行相同的業務邏輯,你想要使用業務邏輯的一個副原本匹配條件,而不是爲每一個case重複業務邏輯
2. 解決方案
將匹配條件放在一行使用|分割
val i = 5 i match { case 1 | 3 | 5 | 7 | 9 => println("odd") case 2 | 4 | 6 | 8 | 10 => println("even") }
同理,該語法也適用於字符串或其餘類型
val cmd = "stop"
cmd match { case "start" | "go" => println("starting") case "stop" | "quit" | "exit" => println("stopping") case _ => println("doing nothing") }
trait Command case object Start extends Command case object Go extends Command case object Stop extends Command case object Whoa extends Command def executeCommand(cmd: Command) = cmd match { case Start | Go => start() case Stop | Whoa => stop() }
2.9. 將match表達式的值賦值給變量
1. 描述
你想要將匹配表達式返回值其賦值給變量,或使用匹配表達式做爲方法的主體
2. 解決方案
在表達式以前插入變量
val evenOrOdd = someNumber match { case 1 | 3 | 5 | 7 | 9 => println("odd") case 2 | 4 | 6 | 8 | 10 => println("even") }
這種方法常常被用做建立短的方法或函數
def isTrue(a: Any) = a match { case 0 | "" => false case _ => true }
2.10 訪問匹配表達式默認狀況下的值
1. 問題描述
你想要訪問默認匹配值,可是使用通配符時沒法訪問到值
2. 解決方案
在默認狀況下指定變量名而非_通配符
i match { case 0 => println("1") case 1 => println("2") case default => println("You gave me: " + default) }
經過給默認匹配變量名稱,你能夠在語句右側訪問該變量
3. 討論
在默認匹配時使用變量名而非通配符,若沒有給出通配符時,可能拋出異常
2.11 在匹配表達式中使用模式匹配
1. 問題描述
你須要在匹配表達式中匹配一個或多個模式,該模式能夠是常量模式、變量模式、構造函數模式、序列模式、元組模式或類型模式等
2. 解決方案
爲要匹配的每一個模式定義一個case語句
def echoWhatYouGaveMe(x: Any): 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 three-element list with 0 as the first element" case List(1, _*) => "a list beginning with 1, having any number of elements" case Vector(1, _*) => "a vector starting with 1, having any number of elements" // tuples 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"found an Alexander, first name = $first" case Dog("Suka") => "found a dog named Suka" // typed patterns case s: String => s"you gave me this string: $s" case i: Int => s"thanks for the int: $i" case f: Float => s"thanks for the float: $f" case a: Array[Int] => s"an array of int: ${a.mkString(",")}" case as: Array[String] => s"an array of strings: ${as.mkString(",")}" case d: Dog => s"dog: ${d.name}" case list: List[_] => s"thanks for the List: $list" case m: Map[_, _] => m.toString // the default wildcard pattern case _ => "Unknown }
上述方法能夠匹配不一樣的類型和模式
3. 討論
一般使用此技術時,你的方法但願傳入的是基類或特性繼承的實例,而後case語句引用該基類型的子類型
import java.io.File sealed trait RandomThing case class RandomFile(f: File) extends RandomThing case class RandomString(s: String) extends RandomThing class RandomNoiseMaker { def makeRandomNoise(t: RandomThing) = t match { case RandomFile(f) => playSoundFile(f) case RandomString(s) => speak(s) } }
sealed表示密封的特質,其子類必須所有給出。
有時你可能須要向模式添加變量,你能夠用下面的通常語法:
變量名 @ 模式
這表示變量綁定模式,這種模式的含義是按照正常模式進行模式匹配,若是模式匹配成功,就像簡單的變量模式同樣,將變量設置爲匹配對象。
當匹配List類型時,咱們能夠以下
case list: List[_] => s"thanks for the List: $list"
當咱們想要匹配以元素1做爲頭元素的列表時,咱們可能會進行以下匹配
case list: List(1, _*) => s"thanks for the List: $list"
但此時會報錯
解決辦法是添加變量綁定模式
case list @ List(1, _*) => s"$list"
2.12. 在匹配表達式中使用案例類
1. 問題描述
你但願在匹配表達式中匹配不一樣的case類(或case對象),如接收actor的消息時
2. 解決方案
根據你的須要可使用以前的不一樣模式進行匹配
trait Animal case class Dog(name: String) extends Animal case class Cat(name: String) extends Animal case object Woodpecker extends Animal object CaseClassTest extends App { def determineType(x: Animal): String = x match { case Dog(moniker) => "Got a Dog, name = " + moniker case _:Cat => "Got a Cat (ignoring the name)" case Woodpecker => "That was a Woodpecker" case _ => "That was something else" } println(determineType(new Dog("Rocky"))) println(determineType(new Cat("Rusty the Cat"))) println(determineType(Woodpecker)) }
2.13 將if表達式(守衛)添加到case語句中
1. 問題描述
你但願將匹配邏輯添加到匹配表達式中的case語句中,例如容許一系列數字或匹配模式,但僅當該模式與某些附加條件相匹配時
2. 解決方案
將if表達式添加至case語句中
i match { case a if 0 to 9 contains a => println("0-9 range: " + a) case b if 10 to 19 contains b => println("10-19 range: " + b) case c if 20 to 29 contains c => println("20-29 range: " + c) case _ => println("Hmmm...") }
能夠匹配不一樣的值
num match { case x if x == 1 => println("one, a lonely number") case x if (x == 2 || x == 3) => println(x) case _ => println("some other value") }
也能夠在匹配時解析出字段信息
def speak(p: Person) = p match { case Person(name) if name == "Fred" => println("Yubba dubba doo") case Person(name) if name == "Bam Bam" => println("Bam bam!") case _ => println("Watch the Flintstones!") }
3. 討論
在進行匹配時也可將if放在=>的右側
case Person(name) => if (name == "Fred") println("Yubba dubba doo") else if (name == "Bam Bam") println("Bam bam!")
2. 14 使用匹配表達式而非isInstanceOf
1. 問題描述
你須要編寫匹配一種類型或多個不一樣類型的代碼塊
2. 解決方案
你可使用isInstanceOf來測試對象的類型
if (x.isInstanceOf[Foo]) { do something ...}
然而並不鼓勵這樣作,更好的辦法是使用匹配表達式
def isPerson(x: Any): Boolean = x match { case p: Person => true case _ => false }
或者給出一個繼承已知父類的子類,須要根據子類的不一樣作出不一樣的操做
trait SentientBeing trait Animal extends SentientBeing case class Dog(name: String) extends Animal case class Person(name: String, age: Int) extends SentientBeing def printInfo(x: SentientBeing) = x match { case Person(name, age) => // handle the Person case Dog(name) => // handle the Dog }
3. 討論
匹配表達式能夠匹配多種類型,而不須要使用isInstanceOf方法
2. 15. 在匹配表達式中使用列表
1. 問題描述
列表數據結構與其餘集合數據結構有點不一樣,它從cons cells開始構建,並以Nil做爲結尾,在使用匹配表達式時你想要利用此特性,例如編寫遞歸程序
2. 解決方案
你可使用以下方法建立List
當編寫遞歸函數時,你能夠利用其尾元素是Nil類型對象
3. 討論
在REPL中構建List時,不要忘記Nil狀況
2.16 使用try/catch匹配一個或多個異常
1. 問題描述
在try/catch塊中你想要匹配一個或多個異常
2. 解決方案
Scala的try/catch/finally與Java類型,可是在catch中使用的是模式匹配
val s = "Foo" try { val i = s.toInt } catch { case e: Exception => e.printStackTrace }
當你須要匹配多個異常時,在catch添加便可
try { openAndReadAFile(filename) } catch { case e: FileNotFoundException => println("Couldn't find that file.") case e: IOException => println("Had an IOException trying to read that file") }
3. 討論
Scala中的catch塊能夠匹配多個異常,若是你不關心發生的異常,可使用以下方式
try { openAndReadAFile("foo") } catch { case t: Throwable => t.printStackTrace() }
try { val i = s.toInt } catch { case _: Throwable => println("exception ignored") }
2.17 在try/catch/finally塊以前聲明變量
1. 問題描述
當你須要調用對象的閉包方法,你想要在try塊中使用一個對象,而且在finally塊中訪問它
2. 解決方案
在try以前將字段聲明爲Option類型,以後在try塊中建立一個Some對象
import java.io._ object CopyBytes extends App { var in = None: Option[FileInputStream] var out = None: Option[FileOutputStream] try { in = Some(new FileInputStream("/tmp/Test.class")) out = Some(new FileOutputStream("/tmp/Test.class.copy")) var c = 0 while ({c = in.get.read; c != −1}) { out.get.write(c) } } catch { case e: IOException => e.printStackTrace } finally { println("entered finally ...") if (in.isDefined) in.get.close if (out.isDefined) out.get.close } }
2.18 自定義控制結構
1. 問題描述
你想要自定義控制結構來改善Scala語言
2. 解決方案
假若有一天你不喜歡while結構,而是以下結構
package foo import com.alvinalexander.controls.Whilst._ object WhilstDemo extends App { var i = 0 whilst (i < 5) { println(i) i += 1 } }
此時,whilist就是你自定義的控制結構,你可能採用以下方法定義whilist方法
def whilst(testCondition: => Boolean)(codeBlock: => Unit) { while (testCondition) { codeBlock } }
可是更好的方法是不調用while循環
package com.leesf.controls import scala.annotation.tailrec object Whilst { // 2nd attempt @tailrec def whilst(testCondition: => Boolean)(codeBlock: => Unit) { if (testCondition) { codeBlock whilst(testCondition)(codeBlock) } } }
3. 討論
在第二版的whilist中,使用了遞歸調用,假設有一個兩個參數的控制結構,當都爲真時執行代碼塊,調用代碼以下
doubleif(age > 18)(numAccidents == 0) { println("Discount!") }
doubleif方法則可定義以下
def doubleif(test1: => Boolean)(test2: => Boolean)(codeBlock: => Unit) { if (test1 && test2) { codeBlock } }
其中,codeBlock: => Unit對應的是println("Discount!")
3、總結
本篇博文學習了Scala的控制結構及其細節,同時還學習了自定義控制結構,也謝謝各位園友的觀看~