1. 程式人生 > >Scala實戰專欄 (三) ——— 控制結構

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等方法

  1. array.foreach(println) ----- 遍歷array陣列,可以使用自定義方法替換println方法
  2. array.foreach( e => println(e.toUpperCase)) ----- 使用匿名函式的語法
  3. array.foreach{ e => | val s = e.toUpperCase | println(s)|} ----- 實現多行函式

3.1.6 for迴圈式是如何被解釋的

提供簡化版的規則,幫助理解for迴圈執行過程

  1. 遍歷集合的一個簡單的for迴圈被解釋為foreach方法呼叫
  2. 帶有衛語句的for迴圈(3.3節)被解釋為一個foreach呼叫後在集合上呼叫withFilter方法的序列
  3. 帶有yield表示式的for迴圈被解釋為集合上呼叫map方法
  4. 帶有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表示式

  1. 開始執行時,for/yield迴圈立刻建立一個新的空集合(Bucket),型別與輸入的集合相同
  2. for迴圈每次遍歷,都會在輸入集合中的每個元素基礎上建立新的元素,加入到Bucket中
  3. 迴圈結束後,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,接受兩個引數列表,第一個是引數列表測試條件,一個表示使用者想要執行的程式碼塊