【Scala】模式匹配和樣本類

模式匹配

要理解模式匹配(pattern-matching),先把這兩個單詞拆開陵珍,先理解什么是模式(pattern)粹污,這里所的模式是數據結構上的拂玻,這個模式用于描述一個結構的組成地粪。

我們很容易聯想到“正則表達”里的模式取募,不錯,這個pattern和正則里的pattern相似驶忌,不過適用范圍更廣矛辕,可以針對各種類型的數據結構笑跛,不像正則表達只是針對字符串付魔。比如正則表達式里 "^A.*" 這個pattern 表示以A開頭、后續(xù)一個或多個字符組成的字符串飞蹂;List("A", _, _*) 也是個pattern几苍,表示第一個元素是”A”,后續(xù)一個或多個元素的List陈哑。

match表達式的不同

match表達式可以看做是Java風格switch的泛化妻坝。當每個模式都是常量并且最后一個模式可以是通配的時候,Java風格的switch可以被自然地表達為match表達式惊窖。但有三點不同需要牢記:

1. match表達式始終以值作為結果刽宪,這是Scala表達式的特點
2. Scala的備選項表達式永遠不會意外掉入到下一個分支。在C或其他類C語言中界酒,每個分支末尾要顯式使用break語句來退出switch圣拄。
3. 如果沒有模式匹配,MatchError異常會被拋出毁欣。這意味著你必須始終確信所有的情況都考慮到庇谆,或者至少意味著可以添加一個默認情況什么事都不做

模式的種類

1、通配模式(_)匹配任意對象凭疮,它被用作默認的“全匹配(catch-all)”的備選項
2饭耳、常量模型僅匹配自身,任何字面量都可以用作常量
3执解、變量模式類似于通配模式寞肖,它可以匹配任意對象。與通配符(_)不同的是衰腌,Scala把變量綁定在匹配的對象上逝淹。

//這里,如果expr非零
//somethingElse變量將綁定對象expr桶唐,結果輸出expr的值
expr match {
    case 0 => "zero"
    case somethingElse => "not zero: " + somethingElse
}

4栅葡、構造器模式提供了深度匹配(deep match),如果備選項是樣本類尤泽,那么構造器模式首先檢查對象是否為該備選項的樣本類實例欣簇,然后檢查對象的構造器參數是否符合額外提供的模式规脸。
構造器模式不只檢查頂層對象是否一致,還會檢查對象的內容是否匹配內層的模式熊咽。由于額外的模式自身可以形成構造器模式莫鸭,因此可以使用它們檢查到對象內部的任意深度。

//某個商店售賣物品横殴,有時物品捆綁在一起打折出售
abstract class Item
case class Product(description: String, price: Double) extends Item
case class Bundle(description: String, discount: Double, items: Item*) extends Item

def price(it: Item): Double = it match {
  case Product(_, p) => p
  case Bundle(_, disc, its @ _*) => its.map(price _).sum * (100-disc) /100
  //這里@表示將嵌套的值綁定到變量its
}


//測試
val bun1 = Bundle("Father's day special", 20.0, Product("Massager", 188.0))
val bun2 = Bundle("Appliances on sale", 10.0, Product("Haier Refrigerato, 3000.0),
                                               Product("Geli air conditionor",2000.0))

//商品組合1 八折結果
scala> price(bun1)
res5: Double = 150.4
//商品組合2 九折結果
scala> price(bun2)
res6: Double = 4500.0

5被因、序列模式可以像匹配樣本類那樣匹配如List或者Array這樣的序列類型。

expr match {
    case List(0, _, _) => println("found it")
    case _ =>
}

//匹配不定長序列
expr match {
    case List(0, _*) => println("found it")
    case _ => 
}

6衫仑、元組模式匹配元祖
7梨与、類型模式可以當做類型測試和類型轉換的簡易替代。

scala> def generalSize(x: Any) = x match {
     |   case s: String => s.length
     |   case m: Map[_, _] => m.size
     |   case _ => 1
     | }
generalSize: (x: Any)Int

scala> generalSize("abc")
res7: Int = 3

scala> generalSize(Map(1 -> 'a', 2 -> 'b'))
res8: Int = 2

scala> generalSize(Math.PI)
res9: Int = 1

樣本類

帶有case修飾符的類稱為樣本類(case class)文狱。這種修飾符可以讓Scala編譯器自動為你的類添加一些句法上的便捷性粥鞋。

  1. 樣本類會添加與類名一致的工廠方法。你不用new關鍵字就可以創(chuàng)建這個類瞄崇。
  1. 樣本類參數列表中的所有參數隱式獲得val前綴呻粹,因此它被當做字段維護。
  2. 編譯器為樣本類添加了方法toString苏研、hashCode和equals的實現等浊。

這些便捷性的代價就是必須寫case修飾符并且樣本類和對象都因為附加的方法及對于每個構造器參數添加了隱含的字段而變得大了一點。
樣本類是一種特殊的類,它經過優(yōu)化以被用于模式匹配。

封閉類

封閉類除了類定義所在的文件之外不能再添加任何新的子類灯荧。其用于模式匹配的另外一個作用是,當你用樣本類來做模式匹配是庄萎,你可能想讓編譯器幫你確保你已經列出了所有可能的選擇。為了達到這個目的塘安,你需要將樣本類的通用超類聲明為sealed糠涛。如果你使用繼承自封閉類的樣本類做匹配,編譯器將通過通知警告信息標識出缺失的模式組合兼犯。
舉個例子:

sealed abstract class Amount

case class Dollar(value: Double) extends Amount
case class Euro(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount

def describe(a: Amount): String = a match {
    case Dollar(_) => "Dollar"
    case Euro(_) => "Euro"
}

//這里會出現編譯器警告
//warning: match may not be exhaustive.
//It would fail on the following input: Currency(_, _)
//       def describe(a: Amount): String = a match {
//                                         ^
//describe: (a: Amount)String

如果想要讓編譯器不進行警告提示的話忍捡,需要給匹配的選擇器表達式添加@unchecked注解。
像是這樣def describe(a: Amount): String = (a: @unchecked) match {切黔。
如果某個類是封閉的砸脊,那么在編譯器所有子類就是可知的,因而編譯器可以檢查模式語句的完整性纬霞。讓所有(同一組)樣本類都擴展某個封閉類或特質是個好的做法凌埂。

Option類型

標準類庫中的Option類型用樣本類來表示那種可能存在、也可能不存在的值诗芜⊥ィ可以是Some(value)的形式埃疫,其中value是實際的值;也可以是None對象孩哑,代表缺失的值栓霜。
Scala集合類的某些標準操作會產生可選值。例如Scala的Map的get方法會發(fā)現了指定鍵的情況下產生Some(value)横蜒,在沒有找到指定鍵的時候產生None胳蛮。
舉例如下:

scala> val capitals = Map("France" -> "Paris",
     | "Japan" -> "Tokyo", "China" -> "Beijing")
capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo, China -> Beijing)

scala> capitals get "France"
res2: Option[String] = Some(Paris)

scala> capitals get "North Pole"
res3: Option[String] = None

樣本類None的形式比空字符串的意圖更加清晰,比使用null來表示缺少某值的做法更加安全丛晌。
Option支持泛型仅炊。舉例來說,Some(Paris)的類型為Option[String]茵乱。

分離可選值最通用的辦法是通過模式匹配的方式茂洒,舉例如下:

scala> def showCapital(x: Option[String]) = x match {
     |   case Some(s) => s
     |   case None => "?"
     | }
showCapital: (x: Option[String])String

scala> showCapital(capitals get "Japan")
res5: String = Tokyo

scala> showCapital(capitals get "France")
res6: String = Paris

scala> showCapital(capitals get "China")
res7: String = Beijing

scala> showCapital(capitals get "North Pole")
res8: String = ?

Scala鼓勵對Option的使用以說明值是可選的孟岛。這種處理可選值的方式有若干超越Java的優(yōu)點瓶竭。

Option[String]類型的變量是可選的String,這比String類型的變量或可能有時是null來說更加明顯
使用可能為null而沒有檢查是否為null的變量產生的編程錯誤在Scala里變?yōu)轭愋湾e誤渠羞,即如果變量是Option[String]類型的斤贰,而你打算當做String使用,這樣不會編譯通過次询。

參考資料

話說模式匹配(1): 什么是模式荧恍?

轉載請注明作者Jason Ding及其出處
GitCafe博客主頁(http://jasonding1354.gitcafe.io/)
Github博客主頁(http://jasonding1354.github.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡書主頁(http://www.reibang.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354進入我的博客主頁

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屯吊,隨后出現的幾起案子送巡,更是在濱河造成了極大的恐慌,老刑警劉巖盒卸,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骗爆,死亡現場離奇詭異,居然都是意外死亡蔽介,警方通過查閱死者的電腦和手機摘投,發(fā)現死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虹蓄,“玉大人犀呼,你說我怎么就攤上這事∞弊椋” “怎么了外臂?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長律胀。 經常有香客問我宋光,道長挑童,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任跃须,我火速辦了婚禮站叼,結果婚禮上,老公的妹妹穿的比我還像新娘菇民。我一直安慰自己尽楔,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布第练。 她就那樣靜靜地躺著阔馋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娇掏。 梳的紋絲不亂的頭發(fā)上呕寝,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音婴梧,去河邊找鬼下梢。 笑死,一個胖子當著我的面吹牛塞蹭,可吹牛的內容都是我干的孽江。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼番电,長吁一口氣:“原來是場噩夢啊……” “哼岗屏!你這毒婦竟也來了?” 一聲冷哼從身側響起漱办,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤这刷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娩井,有當地人在樹林里發(fā)現了一具尸體暇屋,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年撞牢,在試婚紗的時候發(fā)現自己被綠了率碾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡屋彪,死狀恐怖所宰,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情畜挥,我是刑警寧澤仔粥,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響躯泰,放射性物質發(fā)生泄漏谭羔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一麦向、第九天 我趴在偏房一處隱蔽的房頂上張望瘟裸。 院中可真熱鬧,春花似錦诵竭、人聲如沸话告。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沙郭。三九已至,卻和暖如春裳朋,著一層夾襖步出監(jiān)牢的瞬間病线,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工鲤嫡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留送挑,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓泛范,卻偏偏與公主長得像让虐,于是被迫代替她去往敵國和親紊撕。 傳聞我的和親對象是個殘疾皇子罢荡,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容