用過Scala的模式匹配,感覺Java的弱爆了。Scala幾乎可以匹配任何數據類型,如果默認的不能滿足你的要求,你可以自定義模式匹配。
介紹Scala的模式匹配前,我們先了解清楚unapply()與unapplySeq()兩個方法:
名字叫做unapply和unapplySeq的方法在Scala里也是有特殊含義的。
我們前面說過case class在做pattern match時非常好用,而除case class之外,有unapply或unapplySeq方法的對象在pattern match時也有非常好的應用場景。
比方這段代碼:
1 2 3 |
|
我們定義了一個unapply方法,用來計算平方根。
我們能夠像調用普通方法一樣的調用它:
1 2 |
|
這樣會得到36的平方根:6。實際上返回值是Some(6)。
上面的方式是對unapply的浪費。unapply真正的優點是這種:
1 2 3 4 5 |
|
這樣我們無需顯式調用unapply方法,而把是它用在pattern match中。讓編譯器替我們調用它。
當我們寫下這段pattern match的代碼時,編譯器事實上替我們做了好幾件事:
- 調用unapply,傳入number
- 接收返回值並推斷返回值是None,還是Some
- 假設是Some,則將其解開,並將當中的值賦值給n(就是case Square(n)中的n)
這段代碼反編譯出來是這個樣子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
假設沒有unapply方法和pattern match語法之間的這樣的結合,我們自己寫代碼要寫成什么樣子呢?
也許會比上面反編譯的代碼簡單一些,可是顯式地調用開平方的方法。用if else來推斷Option,以及將真正的返回值從Option里面解出來這三件事是免不掉的。
unapplySeq和unapply的作用非常是類似,比如這樣:
1 2 3 4 5 6 |
|
我們定義一個unapplySeq方法,用逗號作為分隔符來把字符串拆開。
然后我們能夠這樣應用它:
1 2 3 4 5 6 7 8 |
|
與上面的樣例非常是類似,只是編譯器在這里替我們做的事情很多其它了:
- 調用unapplySeq,傳入namesString
- 接收返回值並推斷返回值是None,還是Some
- 假設是Some,則將其解開
- 推斷解開之后得到的sequence中的元素的個數是否是三個
- 假設是三個,則把三個元素分別取出,賦值給first,second和third
假設沒有unapplySeq方法和pattern match語法之間的這樣的結合,我們自己寫代碼來做這五件事會顯得非常是繁瑣。
大家了解清楚unapply()與unapplySeq()兩個方法,我就舉個很實用的例子。
現實中往往我們有這樣的一個需求:比如http請求返回體為json字符串,我們往往只需要獲取json中的部分字段值。
這個需求,Java的做法是,必須知道所有json中所有字段及類型,並定義對應的JavaBean,將返回的json字符串先轉換為對應的JavaBean,再通過javaBean獲取想要的字段信息。
那么Scala使用模式匹配就很輕松搞定直接獲取想要的字段值。如下:
先定義一個unapplySeq()實現json的模式匹配(另json字符串插值寫法可參考我以前文章:Scala字符串插值 - StringContext):
package spray.json
import scala.collection.SortedMap
class JsonInterpolation(sc: StringContext) { object json { def apply(args: JsValue*): JsValue = new JsonParser(ParserInput(sc, args), true).parseJsValue() def unapplySeq(input: JsValue): Option[Seq[JsValue]] = { val placeHolders = Seq.range(0, sc.parts.length-1).map(x => JsNumber(Integer.MAX_VALUE - x) ) val pi = ParserInput(sc, placeHolders) val pattern = new JsonParser(pi, true).parseJsValue() val results = collection.mutable.ArrayBuffer[JsValue]() Seq.range(0, sc.parts.length-1).foreach { x => results += null } try { patternMatch(pattern, input, placeHolders, results) Some(results.toSeq) } catch { case ex: Throwable => None } } // TODO report friendly private def patternMatch(pattern: JsValue, input: JsValue, placeHolders: Seq[JsValue], results: collection.mutable.ArrayBuffer[JsValue]): Unit = { def isPlaceHolder(value: JsNumber) = { val num = value.value.toInt val index = Integer.MAX_VALUE - num.toInt num > 0 && index < placeHolders.size && placeHolders(index).eq(value) } pattern match { case x: JsObject => x.fields.foreach { case (key, n @ JsNumber(num)) if isPlaceHolder(n) => val index = Integer.MAX_VALUE - num.toInt assert(input.asJsObject.fields contains key) results(index) = input.asJsObject.fields(key) case (key, value) => assert(input.asJsObject.fields contains key) patternMatch(value, input.asJsObject.fields(key), placeHolders, results) } case x: JsArray => assert(input.isInstanceOf[JsArray]) assert(input.asInstanceOf[JsArray].elements.size >= x.elements.size) x.elements.zipWithIndex.foreach { case (x: JsNumber, y: Int) if isPlaceHolder(x) => val index = Integer.MAX_VALUE - x.value.toInt results(index) = input.asInstanceOf[JsArray].elements(y) case (x: JsValue,y: Int)=> patternMatch(x, input.asInstanceOf[JsArray].elements.apply(y), placeHolders, results) } case x: JsString => assert(x == input) case x: JsBoolean => assert(x == input) case x: JsNumber => assert(x == input) case x@ JsNull => assert(x == input) } } } }
定義好unpplySeq(),那么下面就看下如何通過模式匹配獲取json字符傳中我們關系的字段值:
import spray.json._ /** * 類功能描述: * * @author WangXueXing create at 19-5-11 下午10:37 * @version 1.0.0 */ object ObjectMatch { def main(args: Array[String]): Unit = { val json = json"""{"id": 12333,"sku_no":"sku35352523"}""" json match { case json"""{"sku_no": $skuNo}""" => println(skuNo) case _ => println(json) } } }
輸出:
"sku35352523"
如上,我們就可以選擇性的拿到sku_no字段的值,是不是輕松搞定!