用过Scala的模式匹配,感受Java的弱爆了。Scala几乎能够匹配任何数据类型,若是默认的不能知足你的要求,你能够自定义模式匹配。html
介绍Scala的模式匹配前,咱们先了解清楚unapply()与unapplySeq()两个方法:java
名字叫作unapply和unapplySeq的方法在Scala里也是有特殊含义的。json
咱们前面说过case class在作pattern match时很是好用,而除case class以外,有unapply或unapplySeq方法的对象在pattern match时也有很是好的应用场景。app
比方这段代码:post
1 2 3 |
object Square { def unapply(z: Double): Option[Double] = Some(math.sqrt(z)) } |
咱们定义了一个unapply方法,用来计算平方根。url
咱们可以像调用普通方法同样的调用它:spa
1 2 |
val number: Double = 36.0 Square.unapply(number) |
这样会获得36的平方根:6。实际上返回值是Some(6)。scala
上面的方式是对unapply的浪费。unapply真正的优势是这种:code
1 2 3 4 5 |
val number: Double = 36.0 number match { case Square(n) => println(s"square root of $number is $n") case _ => println("nothing matched") } |
这样咱们无需显式调用unapply方法,而把是它用在pattern match中。让编译器替咱们调用它。htm
当咱们写下这段pattern match的代码时,编译器事实上替咱们作了好几件事:
这段代码反编译出来是这个样子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
double number = 36.0D; double d1 = number; Option localOption = Square..MODULE$.unapply(d1); //调用unapply,传入number BoxedUnit localBoxedUnit; if (localOption.isEmpty()) {//推断返回值是None Predef..MODULE$.println("nothing matched"); localBoxedUnit = BoxedUnit.UNIT; } else {//推断返回值是Some double n = BoxesRunTime.unboxToDouble(localOption.get()); //将Some解开,并将当中的值赋值给n Predef..MODULE$.println(new StringContext(Predef..MODULE$.wrapRefArray((Object[]) new String[] { "square root of ", " is ", "" }) ).s(Predef..MODULE$.genericWrapArray(new Object[] { BoxesRunTime.boxToDouble(number), BoxesRunTime.boxToDouble(n) }))); localBoxedUnit = BoxedUnit.UNIT; } |
假设没有unapply方法和pattern match语法之间的这样的结合,咱们本身写代码要写成什么样子呢?
也许会比上面反编译的代码简单一些,但是显式地调用开平方的方法。用if else来推断Option,以及将真正的返回值从Option里面解出来这三件事是免不掉的。
unapplySeq和unapply的做用很是是相似,好比这样:
1 2 3 4 5 6 |
object Names { def unapplySeq(str: String): Option[Seq[String]] = { if (str.contains(",")) Some(str.split(",")) else None } } |
咱们定义一个unapplySeq方法,用逗号做为分隔符来把字符串拆开。
而后咱们可以这样应用它:
1 2 3 4 5 6 7 8 |
val namesString = "xiao ming,xiao hong,tom" namesString match { case Names(first, second, third) => { println("the string contains three people's names") println(s"$first $second $third") } case _ => println("nothing matched") } |
与上面的样例很是是相似,只是编译器在这里替咱们作的事情不少其它了:
假设没有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字段的值,是否是轻松搞定!