【Scala】Scala之Control Structures

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的控制結構及其細節,同時還學習了自定義控制結構,也謝謝各位園友的觀看~

相關文章
相關標籤/搜索