1. 示例:提取電子郵件地址
定義了unapply(提取)函數的對象就可以稱爲提取器,而apply(注入)不是必須的。
object EMail {
// 注入(可選)
def apply(user: String, domain: String) = user + "@" + domain
// 提取(必須)
def unapply(str: String): Option[(String, String)] = {
val parts = str split "@"
if (parts.length == 2) Some(parts(0), parts(1)) else None
}
}
val EMail(user, domain) = "[email protected]"
println("user: " + user +", domain: " + domain)
運行結果:
同時,因爲定義了apply,可以如下操作:
// 運行:
println(EMail("guyt", "qq.com"))
// 結果:
[email protected]
2. 提取0或1個變量的模式
EMail對象的unapply方法在成功的情況下會返回一對元素值,這很容易想到是否能返回多個元素值?
爲了返回單個模式元素,unappl這是簡單的將結果放入Some:
object Twice {
def apply(s: String): String = s + s
// 如果是連續重複的則返回一半,如DIDI 返回 DI
def unapply(s: String): Option[String] = {
val length = s.length / 2
val half = s.substring(0, length)
if (half == s.substring(length)) Some(half) else None
}
}
也可以定義提取器模式並不綁定任何變量,unapply方法返回布爾值,如:
object UpperCase {
// s.toUpperCase == s 則返回true
def unapply(s: String): Boolean = s.toUpperCase == s
}
這裏使用一個userTwiceUpper函數,在它的模式匹配中選擇應用上面提到的所有提取器:
def userTwiceUpper(s: String) = s match {
case EMail(Twice(user @ UpperCase()), domain) => "match: " + user + " in domain " + domain
case _ => "no match"
}
運行測試:
// 運行:
println(userTwiceUpper("[email protected]"))
println(userTwiceUpper("[email protected]"))
println(userTwiceUpper("[email protected]"))
// 結果:
match: DI in domain qq.com
no match
no match
分析:
這裏博主給出自己的分析:
- 首先EMail對s 應用unapply函數,若成功匹配則返回了第一個參數和第二個參數。第一個參數賦值給Twice 繼續進行匹配,第二個參數賦值給domain。
- 進行Twice匹配,若成功匹配則返回值,將返回的值賦值給UpperCase繼續進行匹配。
- 進行UpperCase的匹配,若成功則返回匹配的對象(注:不是匹配的返回值,正是因爲這樣,UpperCase纔沒有返回布爾值)。user @ UpperCase() 是模式匹配中的變量綁定,這裏@可以當作是語法,如果@的右邊 匹配成功,就將匹配對象賦值給@的左邊。
xxx(省略) s match {
case EMail(Twice(user @ UpperCase()), domain) => "match: " + user + " in domain " + domain
xxx(省略)
}
完整代碼可見GitHub:14-Extractor
3. 提取可變長度參數的模式
就那匹配電子郵箱來說,你可能還想要匹配域名部分中的頂級域名,如:
於unapply不同,要想支持變長參數匹配,需要使用unapplySeq方法,其返回結果必須是Option[Seq[T]]類型
object Domain {
// 注入
def apply(parts: String*): String = parts.reverse.mkString(".")
// 提取
def unapplySeq(whole: String): Option[Seq[String]] = Some(whole.split("\\.").reverse)
}
因此,可以使用Domain提取器來獲取關於電子郵件字符串的更詳細的信息,如要查找名爲tom,後綴爲.com 的郵件地址:
// 查找名爲tom,後綴爲.com 的郵件地址
def isTomInDotCom(s: String): Boolean = s match {
case EMail("tom", Domain("com", _*)) => true
case _ => false
}
測試:
// test
println(isTomInDotCom("[email protected]"))
println(isTomInDotCom("[email protected]"))
println(isTomInDotCom("[email protected]"))
// 結果
true
false
false
到這裏,可以寫出一個新版本的EMail,如:
object ExpandedEMail {
def unapplySeq(email: String): Option[(String, Seq[String])] = {
val parts = email split "@"
if (parts.length == 2)
Some(parts(0), parts(1).split("\\.").reverse)
else
None
}
}
val s = "[email protected]"
val ExpandedEMail(name, topDom, subdoms @ _*) = s
println(name)
println(topDom)
println(subdoms)
// 結果
tom
ch
WrappedArray(epfl, support)
4. 提取器和序列模式
例如,下面語句能正常運行,是因爲List的伴生對象繼承自SeqFactory,而SeqFactory,中定義了unapplySeq方法(如下圖):
5. 正則表達式
如:(-)?(\d+)(.\d*)? 是匹配任意整數、浮點數的
在scala需要這樣定義,注意\ .是需要轉義的:
因爲,scala中""" 包裹的字符串是原生字符串,即\ . 等是其符號本身,不需要轉義:
除此之外,scala還提供StringOps(String 與它存在隱式轉換)又一個名爲.r 的方法,根據這個方法就可以使用字符串獲得正則表達式:
測試正則:
你可以看到這裏使用了三個方法:findAllIn、findFirstIn、findPrefixOf
另外,在正則表達式中我們使用的組(即() 包裹起來的),這裏共使用了三個組,所以又如下的拆解方法: