什么是匹配模式?
模式匹配并不很新淆珊,(上世紀(jì))七十年代中期就已經(jīng)有語(yǔ)言采用夺饲。據(jù)我所知,第一種語(yǔ)言是ML施符,但可能也有更早的語(yǔ)言支持往声。它在許多函數(shù)式語(yǔ)言中都算是標(biāo)準(zhǔn)功能,包括ML操刀、Caml烁挟、Erlang、以及Haskell骨坑。
那么什么是模式匹配呢撼嗓?它可以讓你給一個(gè)值匹配多種情況,有點(diǎn)像Java中的switch語(yǔ)句欢唾。但它不僅可以像switch語(yǔ)句一樣用來匹配數(shù)字且警,還可以匹配對(duì)象的內(nèi)在構(gòu)建形式。
比如礁遣,Scala中的List存在兩種情況:要么是空List斑芜,寫做Nil;要么由一個(gè)head元素緊接著另一List tail組成祟霍。有了模式匹配杏头,你可以詢問:給定的List是空List嗎?只要編寫case Nil沸呐、箭頭(=>)以及后續(xù)表達(dá)式即可:
case Nil => // 后續(xù)表達(dá)式
你還可以詢問:它是非空List嗎醇王?只要編寫case x :: xs、箭頭崭添、以及后續(xù)表達(dá)式即可:
case x :: xs => // 后續(xù)表達(dá)式
雙冒號(hào)(::)表示cons操作符寓娩;x表示List的首元素,xs表示剩余部分呼渣。于是棘伴,模式匹配會(huì)首先區(qū)分List是否為空。而如果List非空屁置,它會(huì)把List的首元素命名為x然后把List剩余部分命名為xs焊夸。接下來,這些變量可以被箭頭右側(cè)表達(dá)式所用蓝角。(參見示例1)
示例1:match表達(dá)式
list match {
case Nil => "was an empty list"
case car :: cdr => "head was " + car + ", tail was " + cdr
}
如果list不為空淳地,將匹配到第二種情況怖糊,List首元素將賦值給x,而列表剩余部分賦值給xs颇象。接下來伍伤,這些變量將被箭頭符號(hào)右側(cè)的字符串連接表達(dá)式所用。例如遣钳,如果list內(nèi)容是List("hello", "world")扰魂,那么匹配表達(dá)式的結(jié)果將是字符串"head was hello, tail was List(world)"。
上例的模式非常簡(jiǎn)單蕴茴。但實(shí)際上模式還支持嵌套劝评,類似表達(dá)式的嵌套,能讓你編寫層數(shù)很深的模式倦淀〗螅總的來說,亮點(diǎn)在于撞叽,模式和表達(dá)式看起來很像姻成。模式本質(zhì)上和表達(dá)式屬于完全一類東西,看上去就像構(gòu)造表達(dá)式一樣愿棋,可以用來構(gòu)造復(fù)雜樹狀對(duì)象科展,但卻不需要編寫new。事實(shí)上糠雨,在Scala中才睹,該對(duì)象構(gòu)造時(shí)一樣不需要new。然后你可以在某些位置填上占位變量甘邀,對(duì)應(yīng)樹對(duì)象中實(shí)際存在的值琅攘。(參見示例2)
示例2:嵌套模式的match表達(dá)式
object match {
case Address(Name(first, last), street, city, state, zip) => println(last + ", " + zip)
case _ => println("not an address") // 默認(rèn)情況
}
在第一種情況下,模式Name(first, last)嵌在模式Address(...)中松邪。last放在了Name構(gòu)造函數(shù)內(nèi)乎澄,可以“提取”出值,因而测摔,可供箭頭右邊的表達(dá)式使用。
因?yàn)槠ヅ涫前l(fā)生在運(yùn)行期的解恰,而且JVM中泛型的類型信息會(huì)被擦掉(跟Java里范型一樣不能匹配)锋八。
case m: Map[String, Int] => ... // 不行,類型不起作用
case m: Map[_, _] => ... // 匹配通用的Map护盈,OK
但對(duì)于數(shù)組來說挟纱,類型信息是完好的,所以可以在Array上匹配腐宋。
對(duì)于嵌套結(jié)構(gòu)紊服,舉例就能一目了然檀轨。
abstarct class Item
case class Article(description: String, price: Double) extends Item
case class Bundle(description: String, price: 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這個(gè)變量被綁定到第一個(gè)Article的description。另外還可以使用@來將值綁定到變量:
// art被綁定為第一個(gè)Article欺嗤,rest是剩余的Item序列
case Bundle(_, _, art @ Article(_, _), rest @ _*) => ...
樣例類
樣例類是種特殊的類参萄,經(jīng)過優(yōu)化以用于模式匹配。
abstract class Amount
// 繼承了普通類的兩個(gè)樣例類
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
// 樣例對(duì)象
case object Nothing extends Amount
使用:
amt match {
case Dollar(v) => "$" + v
case Currency(_, u) => "Oh noes, I got " + u
case Nothing => "" // 樣例對(duì)象沒有()
}
在聲明樣例類時(shí)煎饼,下面的過程自動(dòng)發(fā)生了:
構(gòu)造器的每個(gè)參數(shù)都成為val讹挎,除非顯式被聲明為var,但是并不推薦這么做吆玖;
在伴生對(duì)象中提供了apply方法筒溃,所以可以不使用new關(guān)鍵字就可構(gòu)建對(duì)象;
提供unapply方法使模式匹配可以工作沾乘;
生成toString怜奖、equals、hashCode和copy方法翅阵,除非顯示給出這些方法的定義歪玲。
除了上述之外,樣例類和其他類型完全一樣怎顾,方法字段等读慎。
密封類
當(dāng)使用樣例類來做模式匹配時(shí),如果要讓編譯器確保已經(jīng)列出所有可能的選擇槐雾,可以將樣例類的通用超類聲明為sealed夭委。
密封類的所有子類都必須在與該密封類相同的文件中定義。
如果某個(gè)類是密封的募强,那么在編譯期所有的子類是可知的株灸,因而可以檢查模式語(yǔ)句的完整性。
讓所有同一組的樣例類都擴(kuò)展某個(gè)密封的類或特質(zhì)是個(gè)好的做法擎值。
模式匹配的目的
那么慌烧,為什么你需要模式匹配?我們每個(gè)人都有復(fù)雜的數(shù)據(jù)鸠儿。如果我們堅(jiān)持嚴(yán)格的面向?qū)ο蟮娘L(fēng)格屹蚊,那么我們并不希望直接訪問數(shù)據(jù)內(nèi)部的樹狀結(jié)構(gòu)。相反进每,我們希望調(diào)用方法汹粤,然后在方法中訪問。如果我們能夠這樣做田晚,那么我們就再也不需要模式匹配了嘱兼,因?yàn)檫@些方法已經(jīng)提供了我們需要的功能。但很多情況下贤徒,對(duì)象并不提供我們需要的方法芹壕,而且我們無(wú)法(或者不愿)向這些對(duì)象添加方法汇四。
例如XML。如果給你一棵XML樹踢涌,那么樹就只是單純的數(shù)據(jù)通孽。要么是節(jié)點(diǎn),要么是節(jié)點(diǎn)的序列斯嚎。XML是一種非常通用的數(shù)據(jù)表現(xiàn)形式利虫。例如,DOM本質(zhì)上只是節(jié)點(diǎn)的數(shù)組堡僻,其中每個(gè)節(jié)點(diǎn)的類型都未知】繁梗現(xiàn)在我們?cè)O(shè)想一下,如果把XML樹轉(zhuǎn)換到某種更強(qiáng)的框架中钉疫,可以給你一個(gè)列表硼讽,容納各種不同類型的對(duì)象。組成列表的元素可能包括諸如電話號(hào)碼牲阁、備忘錄或地址等固阁。如果你想以靜態(tài)類型的方式獲取所有這些東西,就會(huì)遇上一個(gè)問題:你不知道每個(gè)元素的類型城菊。在傳統(tǒng)面向?qū)ο蟮木幊陶Z(yǔ)言中备燃,唯一可行方式是,編寫一大堆instanceof檢測(cè)凌唬,一一測(cè)試每個(gè)元素是PhoneNumber實(shí)例并齐、Memo實(shí)例,還是其他實(shí)例客税。一旦這些instanceof語(yǔ)句之一檢測(cè)成功况褪,你還需要進(jìn)行類型轉(zhuǎn)換。上述做法相當(dāng)丑陋和笨拙更耻,有了模式匹配就能避免了测垛。模式匹配能以更安全、更自然的方式完成相同功能秧均。
從本質(zhì)上講食侮,當(dāng)你從外部取得具有結(jié)構(gòu)的對(duì)象圖時(shí),模式匹配就必不可少目胡。你會(huì)在若干情況下遇到這種現(xiàn)象锯七,XML是其中之一。各種從文本解析而來的數(shù)據(jù)讶隐,都屬于這一類。例如久又,有一種典型情況下模式匹配必不可少巫延,即效五,處理編譯器中的抽象語(yǔ)法樹的情況。如果你要對(duì)表達(dá)式進(jìn)行化簡(jiǎn)操作炉峰,表達(dá)式會(huì)被表示為樹畏妖,你需要通過模式匹配對(duì)這些樹進(jìn)行提取操作。類似那樣的情況還有許多疼阔。遇到這些情況時(shí)戒劫,模式匹配真的必不可少。