如下是Scala中C風格switch語句的等效代碼:git
var sign = ... val ch: Char = ... ch match { case '+' => sign = 1 case '-' => sign = -1 case _ => sign = 0 }
在這裏,case _ 與 C 語言的 default 相同,能夠匹配任意的模式,因此要注意放在最後。有這樣一個能捕獲全部模式是有好處的。若是沒有模式可以匹配,代碼會拋出MatchError。正則表達式
C 語言的 switch中的case語句必須使用break才能推出當前的分支,不然會繼續執行後面的分支,直到遇到break或者結束; 而Scala的模式匹配只會匹配到一個分支,不須要使用break語句,由於它不會掉入到下一個分支。數組
match是表達式,與if同樣,是有值的:安全
sign = ch match { case '+' => 1 case '-' => -1 case _ => 0 }
用|來分隔多個選項:app
prefix match{ case "0" | "0x" | "0X" => ... ... }
你能夠在match表達式中使用任何類型,而不只僅是數字。函數
在C語言中,若是你想用switch判斷字符是數字,則必須這麼寫:優化
switch(ch) { case '0': ... case '9': do something; break; default: ...; }
你要寫10條case語句才能夠匹配全部的數字;而在Scala中,你只須要給模式添加守衛:spa
ch match { case '+' => 1 case '-' => -1 case _ if Character.isDigit(ch) => digit = Character.digit(ch, 10) case _ => 0 }
模式老是自上而下進行匹配。scala
若是case關鍵字後面跟着一個變量名,那麼匹配的表達式會被賦值給那個變量。rest
str(i) match { case '+' => 1 case '-' => -1 case ch => digit = Character.digit(ch, 10) }
// 在守衛中使用變量
str(i) match { case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10) ... }
注意: Scala是如何在模式匹配中區分模式是常量仍是變量表達式: 規則是變量必須是以小寫字母開頭的。 若是你想使用小寫字母開頭的常量,則須要將它包在反單引號中。
你能夠對錶達式的類型進行匹配,例如:
obj match { case x: Int => x case s: String => Integer.parseInt(s) case _: BigInt => Int.MaxValue case - => 0 }
此時obj對象的類型必須是模式匹配中全部類型公共的超類,不然報錯。
在Scala中咱們更傾向於選擇模式匹配而不是isInstanceOf/asInstanceOf。
注意:當你在匹配類型的時候,必須給出一個變量名,不然你將會拿對象自己來進行匹配:
obj match { case _: BigInt => Int.MaxValue // 匹配任何類型爲BigInt的對象 case BigInt => -1 // 匹配類型爲Class的BigInt對象 }
注意: 匹配發生在運行期,Java虛擬機中泛型的類型信息是被擦掉的。所以,你不能用類型來匹配特定的Map類型。
case m: Map[String, Int] => ... // error // 能夠匹配一個通用的映射 case m: Map[_, _] => ... // OK // 可是數組做爲特殊狀況,它的類型信息是無缺的,能夠匹配到Array[Int] case m: Array[Int] => ... // OK
要匹配數組的內容,能夠在模式中使用Array表達式:
arr match { case Array(0) => "0" // 任何包含0的數組 case Array(x, y) => x + " " + y // 任何只有兩個元素的數組,並將兩個元素本別綁定到變量x 和 y case Array(0, _*) => "0 ..." // 任何以0開始的數組 case _ => "Something else" }
若是你想講匹配到 _* 的變長度參數綁定到變量,你能夠用 @ 表示法,就像這樣:
case Array(x,rest @ _*) => rest.min
一樣也能夠應用到List。或者你也可使用::操做符
lst match { case 0 :: Nil => "0" case x :: y :: Nil => x + " " + y case 0 :: tail => "0 ..." case _ => "Something else" }
對於元組:
pair match { case (0, _) => "0, ..." case (y, 0) => y + " 0" case _ => "neither is 0" }
說明:若是模式有不一樣的可選分支,你就不能使用除下劃線外的其餘變量命名。
pair match{ case (_,0) | (0,_) => ... //ok 若是其中一個是0 case (x,0) | (0,x) => ... //錯誤——不能對可選分支作變量綁定 }
在上面的模式是如何匹配數組、列表、元組的呢?Scala是使用了提取器機制----帶有從對象中提取值的unapply 或 unapplySeq方法的對象。其中,unapply方法用於提取固定數量的對象;而unapplySeq提取的是一個序列,可長可短。
arr match { case Array(0, x) => ... // 匹配有兩個元素的數組,其中第一個元素是0,第二個綁定給x }
Array伴生對象就是一個提取器----它定義了一個unapplySeq方法。該方法執行時爲:Array.unapplySeq(arr) 產出一個序列的值。第一個值於0進行比較,第二個賦值給x。
正則表達式也能夠用於提取器的場景。若是正則表達式有分組,能夠用模式提取器來匹配每一個分組:
val pattern = "([0-9]+) ([a-z]+)".r "99 bottles" match { case pattern(num, item) => ... // 將num設爲99, item設爲"bottles" } pattern.unapplySeq("99",bottles)交出的是一系列匹配分組的字符創。這些字符串被分別賦值給了num和item。
注意: 在這裏提取器並非一個伴生對象,而是一個正則表達式對象。
在變量聲明中也可使用變量的模式匹配:
val (x, y) = (1, 2) // 把x定義爲1, 把y定義爲2. val (q, r) = BigInt(10) /% 3 // 匹配返回對偶的函數 // 匹配任何帶有變量的模式 val Array(first, second, _*) = arr
上述代碼將數組arr的第一個和第二個元素分別賦值給了first和second,並將剩餘的元素做爲一個Seq複製給了rest
你能夠在for推導式中使用帶變量的模式。
import scala.collection.JavaConversions.propertiesAsScalaMap for ((k, v) <- system.getProperties()) { println(k + " -> " + v) }
對應映射每個(鍵,值)對偶,k被綁定到鍵,而v被綁定到值。
在for推導式中,失敗的匹配將被安靜的忽略。例如:
// 只匹配值爲空的狀況 for ((k, "") <- system.getProperties()) { println(k) }
你也可使用守衛。注意if關鍵字出如今 <- 以後。
for ((k, v) <- system.getProperties() if v == "") { println(k) }
樣例類是一種特殊的類,它們通過優化以被用於模式匹配。
abstract class Amount case class Dollar(value; Double) extends Amount case class Currency(value: Double, unit: String) extends Amount // 針對單例的樣例對象 case object Nothing extends Amount // 將Amount類型的對象用模式匹配來匹配到它的類型,並將屬性值綁定到變量: amt match { case Dollar(v) => "$" + v case Currency(_, u) => "Oh noes, I got " + u case Nothing => "" }
當你聲明樣例類時,以下事情會自動發生:
除了上述節點外,樣例類和其餘類徹底同樣。你能夠添加方法和字段,擴展他們,等等。
樣例類的copy方法建立一個與現有對象值相同的新對象。例如:
val amt = Currency(29.95, "EUR") val price = amy.copy() // 生成了一個新的Currency(29.95, "EUR")對象 val price2 = amt.copy(value = 19.95) //至關於執行了 Currency(19.95, "EUR") val price3 = amt.copy(unit = "CHF") //至關於執行了 Currency(29.95, "CHF")
若是unapply方法產出一個對偶,則能夠在case語句中使用中置表示法。尤爲是對於兩個參數的樣例類,你可使用中置表示法來表示它。
amt match { case a Currency u => ... } // 等同於 case Currency(a, u)
這個特性的本意是要匹配序列。例如:每一個List對象要麼是Nil,要麼是樣例類::, 定義以下:
case class ::[E](head: E, tail: List[E]) extends List[E] // 所以你能夠這麼寫 lst match { case h :: t => ... // 等同於 case ::(h, t), 將調用::.unapply(lst) }
說明:中置表示法用於任何返回對偶的unapply方法。如下是一個示例:
case object +: { def unapply[T](input: List[T]) = if (input.isEmpty) None else Some((input.head, input.tail)) }
這樣一來你就能夠用+:來構析列表了
1 +: 7 +: 2 +: 9 +: Nil match{ case first +: second +: rest => first + second + rest.length }
樣例類常常被用於嵌套結構。例如,某個商店收買的物品。有時,咱們會將物品捆綁在一塊兒打折出售。
abstract class Item //物品樣例類參數爲 描述、物品價格 case class Article(description: String, price: Double) extends Item //減價出售物品樣例類參數爲 描述、折扣、物品變長參數 case class Bundle(description: String, discount: Double, items: Item*) extends Item // 產生嵌套對象 Bundle("Father's day special", 20.0, Article("Scala for the Impatient", 39.95), Bundle("Anchor Distillery Sampler", 10.0, Article("Old Potrero Straight Rye Whisky", 79.95), Article("Junipero Gin", 32.95) ) ) // 模式匹配到特定的嵌套,好比: case Bundle(_, _, Article(descr, _), _*) => ...
上述代碼將descr綁定到Bundle的第一個Article的描述。你也能夠 @ 表示法將嵌套的值綁定到變量
case Bundle(_, _, art @ Article(_, _), rest @ _*) => ...
這樣,art就是Bundle中的第一個Article, 而rest則是剩餘Item的序列。 _*表明剩餘的Item。
該特性實際應用,如下是一個計算某Item價格的函數
def price(it: Item): Double = it match { case Article(_, p) => p case Bundle(_, disc, its @ _*) => its.map(price _).sum - disc }
樣例類適用於那種標記了不會改變的結構。例如Scala的List就是用樣例類實現的。
abstract class List case object Nil extends List case class ::(head: Any, tail: List) extends List
當用在合適的地方時,樣例類是十分便捷的,緣由以下:
對於樣例類:
case class Currency(value: Double, unit: String)
一個Currency(10, "EUR")和任何其餘Currency(10, "EUR")都是等效的,這也是equals和hashCode方法實現的依據。這樣的類一般都是不可變的。對於那些帶有可變字段的樣例類,咱們老是從那些不會改變的字段來計算和得出其哈希值,好比用ID字段。
密封類是指用sealed修飾的類。密封類的全部子類都必須在與該密封類相同的文件中定義。這樣作的好處是:當你用樣例類來作模式匹配時,你可讓編譯器確保你已經列出了全部可能的選擇,編譯器能夠檢查模式語句的完整性。
sealed abstract class Amount case class Dollar(value: Double) extends Amount case class Currency(value: Double, unit: String) extends Amunt
舉例來講,若是有人想要爲歐元添加另外一個樣例類:
case class Euro(value: Double) extends Amount
那麼,上述的樣例類必須與Amount類在一個文件中。
標準類庫中的Option類型用樣例類來表示那種可能存在也可能不存在的值。樣例子類Some包裝了某個值,例如:Some("Fred")。而樣例對象None表示沒有值。
這筆使用空字符串的意圖更加清晰,比使用null來表示缺乏某值得作法更加安全。
Option支持泛型。舉例來講Some("Fred")的類型爲Option[String]。
Map類的get方法返回一個Option。若是對於給定的鍵沒有值,則get返回None。若是有值,就會將改值包裝在Some中返回。
你能夠用模式匹配來分析這樣一個值:
val p = scores.get("Alice") p match{ case Some(score) => println(score) case None => println("No score") }
有點麻煩,你也可使用isEmpty和get:
if(p.isEmpty) println("No score") else println(p.get)
這也很麻煩。用getOrElse更好:
println(p.getOrElse("No score")) //若是p爲None,getOrElse將返回No score
處理可選值(Option)更強力的方式是將他們當作擁有0或1個元素的集合。你能夠用for循環來訪問這個元素:
for(score <- p) println(score)
若是p是None,則什麼都不會發生。若是他是一個Some,那麼循環將被執行,而sorce將會被綁上可選值內容。
你也能夠用諸如map、filter或foreach方法。例如:
val b = p.map( _ + 1) //Some(score + 1) 或 None val a = p.filter(_ > 5) //若是score > 5,則獲得Some(score ),不然獲得None p.foreach(println _) //若是存在,打印score值
提示:在從一個可能爲null的值建立Option時,你可能簡單地使用Option(value)。若是value爲null,結果就是None;其他狀況獲得Some(value)。
被包含在花括號內的一組case語句是一個偏函數,一個並不是對全部輸入值都有定義的函數。他是PartialFunction[A, B]類的一個實例(A是參數類型、B是返回類型)該類有兩個方法:apply方法從匹配到的模式計算函數值,而isDefinedAt方法在輸出至少匹配其中一個模式時返回true。
例如:
val f: PartialFunction[Char, Int] = { case '+' => 1; case '-' => -1 } f('-') // 調用 f.apply('-'), 返回-1 f.isDefinedAt('0') // fase f('0') // 拋出MatchError
有一些方法接收PartialFunction做爲參數。舉例來講,GenTraversable特質的collect方法將一個偏函數應用到全部在該偏函數有定義的元素,並返回包含這些結果的序列。
"-3+4".collect {case '+' => 1; case '-' => -1 } // Vector(-1, 1)
Seq[A]是一個PartialFunction[Int,A],而Map[K,V]是一個PartialFunction[K,V]。例如,你能夠將映射傳入collect:
val names = Array("Alice","Bob","Carmen") var scores = Map("Alice"->10,"Carmen"→7) names.collects(scores ) //將交出Array(10,7)
lift方法將PartialFunction[T,R]變成一個返回類型爲Option[R]的常規函數。
var f :PartialFunction[Char, Int] = {case '+' => 1;case '-' => -1} var g = f.lift //一個類型爲Char => Option[Int]的函數
這樣一來,g('-')獲得Some(-1),而g('*')獲得None。
相反,你也能夠調用Function.unlift將Option[R]的函數變成一個偏函數。
說明:try語句的catch字句是一個偏函數。你甚至可使用一個持有函數的變量。
def tryCatch[T](b: => T, catcher: PartialFunction[Throwable, T]) = try {b} catch catcher
而後,你就能夠像以下這樣提供一個定製的catch子句:
val result = tryCatch(str.toInt, { case _: NumberFormatException => -1})