- 到目前為止所使用的模式匹配的拆解和匹配都建立在
case class
的基礎(chǔ)上,如果要?jiǎng)?chuàng)建自己的模式而不定義case class
绣檬,提取器提供了一種實(shí)現(xiàn)手段。
提取器
- 在
Scala
中小压,只要有一個(gè)object
擁有unapply
的方法玉工,該object
就被稱為提取器,這個(gè)unapply
的作用是跟selector
進(jìn)行匹配汁胆,如果匹配成功梭姓,就將其拆解開(kāi)來(lái)。
object EMail { // The injection method (optional) def apply(user: String, domain: String) = user + "@" + domain // The extraction method (mandatory) def unapply(str: String): Option[(String, String)] = { val parts = str split "@" if (parts.length == 2) Some(parts(0), parts(1)) else None } }
定義了一個(gè)名為EMail
的提取器嫩码。unapply
方法是apply
方法的逆操作誉尖,unapply
的入?yún)?code>email地址,返回user
和domain
铸题,但是為了處理字符串不是email
的情況铡恕,將unapply
的返回值定義為Option
類型琢感。在模式匹配中,如果case
后面遇到了提取器探熔,就調(diào)用unapply
方法驹针,其入?yún)⑹沁x擇表達(dá)式。
selectorString match { case EMail(user, domain) => ... } EMail.unapply(selectorString)
unapply
方法的返回值必須是Option
類型的诀艰。這里的case Email(param1)
模式柬甥,如果沒(méi)有定義apply
方法,有兩種寫(xiě)法其垄,第一種是Email(param1)
苛蒲,這個(gè)param1
是unapply
返回的整個(gè)元組;第二種就是Email(param1, param2, ……)
绿满,參數(shù)的個(gè)數(shù)和unapply
定義的返回參數(shù)個(gè)數(shù)相同臂外。定義了apply
方法也是同樣的情況。
目前選擇器selectorString
的類型和提取器unapply
的參數(shù)類型是一致的喇颁,都是String
漏健,但這并不是必須的,selectorString
可以為任何類型无牵,unapply
會(huì)首先檢查selectorString
的類型是不是String
類型的漾肮,apply
和unapply
是對(duì)偶的,如果兩者存在于一個(gè)對(duì)象中的話茎毁,Email.unapply(Email.apply(user, domain))
的返回值是Some(user, domain)
克懊,apply
使用參數(shù)來(lái)構(gòu)建對(duì)象,unapply
將對(duì)象解析為構(gòu)造參數(shù)七蜘。推薦將兩者放在一起谭溉。
0個(gè)或者1個(gè)變量的模式
- 如果返回多個(gè)模式元素,可將這個(gè)元素放在元組中橡卤,并使用
Some
包裹起來(lái)進(jìn)行返回扮念,如果要返回單個(gè)元素,直接使用Some
將這個(gè)元素進(jìn)行包裝碧库。如果不返回元素柜与,則返回布爾值。
object UpperCase { def unapply(s: String): Boolean = s.toUpperCase == s }
case EMail(Twice(x @ UpperCase()), domain)
中含有三個(gè)unapply
模式嵌灰。UpperCase
的()
要帶上弄匕,不然匹配的就是UpperCase
自身的類型。UpperCase
自身雖然沒(méi)有綁定任何變量沽瞭,但可以使用變量綁定@
將跟它匹配的模式關(guān)聯(lián)一個(gè)變量迁匠,DIDI@xiaomi.com
返回的x
就是DI
,可見(jiàn)嵌套的模式匹配是從最外層進(jìn)行的。
提取可變長(zhǎng)度參數(shù)的模式
如何使用提取器支持下面的代碼:
dom match { case Domain("org", "acm") => println("acm.org") case Domain("com", "sun", "java") => println("java.sun.com") case Domain("net", _*) => println("a .net domain") }
就是一個(gè)提取器可生成多個(gè)匹配模式城丧,使用unapplySeq
方法延曙。
def unapplySeq(whole: String): Option[Seq[String]] Some(whole.split("\\.").reverse)
返回的是Option[Seq[String]]
類型。
def unapplySeq(email: String): Option[(String, Seq[String])]
既能夠返回固定元素String
亡哄,也能夠返回不定元素枝缔,不定元素需要寫(xiě)在最后面。這里的Seq
也可以換成List蚊惯,Array魂仍,IndexedSeq
等。
提取器和序列模式
- 列表模式其實(shí)就是在
List
中存在一個(gè)unapplySeq
方法拣挪。Set
和Map
應(yīng)該是不可以的。因?yàn)樵诒容^的時(shí)候是有序的俱诸。
提取器和樣例類的比較
- 樣例類將數(shù)據(jù)的具體實(shí)現(xiàn)細(xì)節(jié)暴露給了使用方菠劝。在能夠正確匹配構(gòu)造器模式的情況下,選擇器
selector
的具體實(shí)現(xiàn)細(xì)節(jié)是暴露的睁搭。 - 提取器則隱藏了數(shù)據(jù)的具體實(shí)現(xiàn)赶诊,既可以使用模式匹配又可以隱藏,模式可以跟數(shù)據(jù)類型無(wú)關(guān)园骆,這種特性稱之為表現(xiàn)獨(dú)立舔痪,在大型的開(kāi)發(fā)系統(tǒng)中是非常有必要的,可以更改實(shí)現(xiàn)細(xì)節(jié)而不影響客戶端的實(shí)現(xiàn)锌唾。
- 表現(xiàn)獨(dú)立是提取器相對(duì)于樣例類一個(gè)重要的優(yōu)勢(shì)锄码,樣例類相對(duì)于提取器的優(yōu)點(diǎn)主要有:樣例類更好實(shí)現(xiàn),編譯器可以更好的優(yōu)化晌涕,因?yàn)?code>case class的實(shí)現(xiàn)是固定的滋捶,但是提取器中可以寫(xiě)任何代碼,最后余黎,如果
case class
繼承自sealed trait
或者sealed class
重窟,則在進(jìn)行模式匹配的時(shí)候編譯器可以幫助檢查所有的情況。 - 如果是封閉的應(yīng)用惧财,則使用樣例類是更好的巡扇,因?yàn)榉奖悖€可以提供靜態(tài)檢查垮衷;如果類的繼承關(guān)系是需要改變的厅翔,且需要暴露給使用方,提取器是不錯(cuò)的選擇帘靡,保持了表現(xiàn)獨(dú)立知给。
正則表達(dá)式
- 提取器的另外一個(gè)應(yīng)用場(chǎng)景是正則表達(dá)式,
Scala
跟Java
一樣,通過(guò)一個(gè)類庫(kù)來(lái)提供對(duì)正則表達(dá)式的支持涩赢。 -
scala
的正則表達(dá)式支持在scala.util.matching
中戈次。新的正則表達(dá)式是將一個(gè)字符串傳遞給Regex
構(gòu)造方法來(lái)完成的,在String
中需要對(duì)特殊字符進(jìn)行轉(zhuǎn)義筒扒。也可以直接在""""""
中寫(xiě)入原生字符串怯邪,比如"""(-)?(\d+)(\.\d*)?"""
scala> val Decimal = new Regex("""(-)?(\d+)(\.\d*)?""")
或者
val Decimal = """(-)?(\d+)(\.\d*)?""".r
,因?yàn)樵?code>StringOps中存在一個(gè)名為r
的方法可以生成Regex
表達(dá)式花墩。
查找正則表達(dá)式
- 可以使用不同的操作符悬秉,在字符串中查找正確的正則表達(dá)式
-
regex findFirstIn str
,在str
中查找regex
冰蘑,返回Option
類型和泌;
-
-
regex findAllIn str
,在str
中查找regex
祠肥,返回Iterator
類型武氓;
-
-
regex findPrefixOf str
,在str
的一開(kāi)始查找regex
仇箱,返回Option
類型县恕。
-
使用正則表達(dá)式提取信息
- 每一個(gè)正則表達(dá)式都有對(duì)應(yīng)的提取器,用來(lái)表示正則表達(dá)式中匹配的
組
字符串剂桥。
scala> val Decimal(sign, integerpart, decimalpart) = "-1.23" sign: String = - integerpart: String = 1 decimalpart: String = .23
可綁定的變量是正則表達(dá)式中的各個(gè)組忠烛。
- 提取器泛化了模式匹配,允許定義自己的模式权逗,并不需要跟
selector
的類型相關(guān)美尸,同時(shí)提取器在模式和數(shù)據(jù)的具體表現(xiàn)形式之間增加了一層保護(hù)層,使得大型軟件的實(shí)現(xiàn)更為靈活旬迹,保持了表現(xiàn)獨(dú)立性火惊。