14 模式匹配和樣例類


模式匹配是一個很強大的機制,可以應用在很多場合,switch語句、類型檢查、獲取複雜表達式的不同部分,樣例類針對模式匹配進行了優化。

模式匹配

  • match case,捕捉到第一個分支就不會再繼續其他的分支
var signs = new collection.mutable.ArrayBuffer[String]
val s = "+1-2+3-4"
for (ch <- s) {
  ch match {
    case '+' => signs += "正"
    case '-' => signs += "負"
    case _ => ()
  }
}
println(signs)
//ArrayBuffer(正, 負, 正, 負)
  • match也是表達式,也是有值,可以用 | 來分隔多個選項
  • match表達式可以使用任何類型
var sign = 0
var ch = '>'
sign = ch match {
  case '+' | ';' | ',' => 1
  case '-' | '>' | '<' => -1
  case _ => 0
}

守衛

  • 使用if語句,可以帶任何Boolean條件
var ch = '3'
sign = ch match {
  case _ if (ch.isDigit) => Character.digit(ch,10)
  case '-' | '>' | '<' => -1
  case _ => 0
}

變量模式

  • 如果case關鍵字後面跟着一個變量名,那麼匹配的表達式會被賦值給那個變量
  • 將case _ 看做變量名爲 _ 的情況
  • 變量模式可能會與常量表達式衝突,規則是變量必須以小寫字母開頭,如果有個小寫字母開頭的常量,需要將其放在反引號中。如果變量以大寫字母開頭,會報錯not found value
var ch = 'a'
ch match {
  case _ if (ch.isDigit) => Character.digit(ch,10)
  case '-' | '>' | '<' => -1
  case ch1 =>  println("haha")
  case _ => println("no")
}
//守衛中使用變量
var ch = '4'
ch match {
  case num if (num.isDigit) => println(s"${num} is a number")
  case '-' | '>' | '<' => -1
  case ch1 =>  println("haha")
  case _ => println("no")
}

類型模式

  • 對錶達式的類型進行匹配
  • 匹配類型的時候,必須給出一個變量名,否則將拿對象本身來進行匹配
obj match {
  case x:Int => x
  case s:String => Integer.parseInt(s)
  case _:BigInt => Int.MaxValue
  case _ =>0
}
  • 匹配發生在運行期,Java虛擬機中泛型的類型信息是被擦掉的,因此不能用類型來匹配特定的Map類型,例如case m:Map[String,Int] =>,但是可以忽略類型case m:Map[,] =>
  • 但是可以匹配到數組,數組的類型信息是完好的。
  • 在下面的例子中,雖然類型不匹配,還是會打印map
val obj = Map("A"->"a","B"->"b")
obj match {
  case _:Map[String,Int] => println("map")
  case _ =>0
}

匹配數組、列表和元組

  • 匹配數組,使用Array表達式,匹配列表使用List表達式,也可以使用::
  • 匹配到可變長度參數可以綁定到變量
val arr = Array(1,2,356)
arr match {
  case _ if arr.contains(0) => println("contains 0") //包含0
  case Array(1,_*) => println("startswith 1")  //第一個元素是1
  case Array(1,rest @ _*) => println(rest)  //第一個元素是1,其他元素Vector(2, 356)
  case Array(x,y) => println("only 2 elements") //只包含兩個元素
  case _ => println("somthing else")
}
  • 列表
val lst =List(0,1,2,3)
lst match{
  case x::y::Nil =>println("only 2 elements")
  case 0::tail => println("0開頭")
  case _ => ()
}
  • 元組
val pair = (0,1)
pair match {
  case (1, _) => println("1開頭")
  case (x,y) => println("only 2 elements")
  case _ => ()
}
  • 注意變量是如何綁定到列表或元組的不同部分的,這種綁定可以輕鬆地訪問複雜結構的組成部分,因此這樣的操作被稱爲析構
  • 模式匹配中的變量必須以小寫字母開頭。
  • 如果模式中有不同的可選分支,就不能使用除下劃線外的其他變量名
pair match {
  case (_,0) | (0,_) => println("Contains 0")
  //case (x,0) | (0,x) => println("a")錯誤
  //case (x,0) | (0,y) => println("Contains 0") 錯誤
  case _ => ()
}

提取器

  • 模式匹配(數組、列表、元組)的背後是提取器機制(extrator機制),使用unapply或unapplySeq方法,unapply方法用於提取固定數量的對象,unapplySeq用於提取一個序列,可長可短
  • 例如在Array的伴生對象中定義unapplySeq方法,前面出現的,rest是unapplySeq方法
val arr = Array(1,2,356)
arr match {
  case _ if arr.contains(0) => println("contains 0") //包含0
  case Array(1,_*) => println("startswith 1")  //第一個元素是1
  case Array(1,rest @ _*) => println(rest)  //第一個元素是1,其他元素Vector(2, 356)
  case Array(x,y) => println("only 2 elements") //只包含兩個元素
  case _ => println("somthing else")
}
  • 另一個適合使用提取器的場景是正則表達式
  • 這裏的提取器不是unapply,而是正則表達式對象
val pattern = "([0-9]+) ([a-z]+)".r
"99 bottles" match{
  case pattern(num,item) => println(s"${num}---${item}")
}

變量聲明中的模式

  • 模式可以帶變量
  • 同樣,變量不要以大寫開頭
scala> val (x,y) = (1,2)
x: Int = 1
y: Int = 2

scala> val (q,r) = BigInt(10) /% 3
q: scala.math.BigInt = 3
r: scala.math.BigInt = 1

scala> val Array(a,b,rest @ _*)=Array(3,1,4,2,6,5,7)
a: Int = 3
b: Int = 1
rest: Seq[Int] = Vector(4, 2, 6, 5, 7)

scala> val Array(A,B,rest @ _*)=Array(3,1,4,2,6,5,7)
<console>:12: error: not found: value A
       val Array(A,B,rest @ _*)=Array(3,1,4,2,6,5,7)
                 ^
<console>:12: error: not found: value B
       val Array(A,B,rest @ _*)=Array(3,1,4,2,6,5,7)
//定義一個常量       
scala> final val A = 20
A: Int(20) = 20
//匹配到Array(0)等於常量A的時候,繼續匹配後面的
scala> val Array(A,b,rest @ _*)=Array(20,1,4,2,6,5,7)
b: Int = 1
rest: Seq[Int] = Vector(4, 2, 6, 5, 7)
//Array(0)不等於常量A的時候,報錯了
scala> val Array(A,b,rest @ _*)=Array(290,1,4,2,6,5,7)
scala.MatchError: [I@1e186006 (of class [I)
  ... 32 elided

for表達式的模式

  • for的變量模式
  • 失敗的鍵將會被忽略
    import scala.collection.JavaConversions.propertiesAsScalaMap
    for((k,v) <- System.getProperties()){println(s"${k}--->${v}")}

樣例類

  • 樣例類是一種特殊的類,經過優化以用於模式匹配
  • 可以用模式匹配來匹配樣例類的類型和屬性值
  • 樣例類的實例使用(),樣例對象不需要使用括號
abstract class Amount
case class Dollar(value:Double) extends Amount
case class Currency(value:Double,unit:String) extends Amount
//樣例對象
case object Nothings extends Amount
val amt1 = Dollar(120)
val amt2 = Currency(23,"Dollar")
val amts = Array(amt1,amt2)

for (amt <- amts) {
  amt match {
    case Dollar(v) => println(s"$$${v}")
    case Currency(_, v) => println(s"I got ${v}")
    case Nothings => ()
  }
}
  • 聲明樣例類時,有幾件事會自動發生:
  1. 構造器中的每一個參數都聲明爲val,除非它被顯式地聲明爲var(但不建議這樣做)
  2. 在伴生對象中提供apply方法,不需要new就可以構造相應的對象
  3. 提供unapply方法讓模式匹配工作
  4. 生成toString,equals,hashCode和copy方法,除非顯式地給出這些方法的定義
  • 除了上面的幾點外,樣例類和其他類一樣,可以添加方法和字段,擴展等

copy方法和帶名參數

  • copy方法創建一個與現有對象值相同的新對象
  • 因爲Currency對象本身不可變,可以共享其引用,可以用帶名參數修改某些屬性
scala> val price = amt2.copy()
price: Currency = Currency(23.0,Dollar)

scala> val price1 = amt2.copy(unit="EUR")
price1: Currency = Currency(23.0,EUR)

case語句的中置表示法

  • 如果unapply方法交出的是一個對偶,可以在case語句中使用中置表示法,尤其是對於兩個參數的樣例類
scala> amt2 match{case a Currency u => println(s"${a}  ${u}")}
23.0  Dollar

匹配嵌套結構

  • 根據樣例類定義幾個嵌套對象
  • 模式匹配特定的嵌套
  • 用@表示法將嵌套的值綁定到變量
abstract class Item
case class Article(description:String,price:Double) extends Item
case class Bundle(description:String,discount:Double,items:Item*) extends Item

val somes = Bundle("Father's day special",5,Article("Scala quickly",30),
  Bundle("Anchor Distillery Sumpler",10.0,Article("Old Potre",40),Article("Junipero Gin",50)))
somes match{
  case Bundle(_,_,Article(desc,_),_*) => println(desc)
}
//Scala quickly
somes match {
  case Bundle(_,_,art @ Article(_,_),rest @ _*) => println(rest)
}
//WrappedArray(Bundle(Anchor Distillery Sumpler,10.0,WrappedArray(Article(Old Potre,79.95), Article(Junipero Gin,32.95))))
  • 計算價格的函數
def price(it:Item):Double=it match{
  case Article(_,p) => p
  case Bundle(_,disc,its @ _*) => its.map(price _).sum -disc
}
//修改完全
def price(it:Item):Double=it match{
  case Article(_,p) => p
  case Bundle(_,disc,its @ _*) => its.map(x=>price(x)).sum -disc
}
println(price(somes))  //105
  • 樣例類適用於那些不會被改變的結構,例如List
  • 樣例類便捷之處:
  1. 模式匹配
  2. 構造時無需使用new
  3. 自動實現了toString,equals,hashCode,copy方法
  • 擴展其他樣例類的樣例類而言,toString,equals,hashCode,copy方法不會被生成
  • 如果需要多層次的繼承來將樣例類的通用行爲抽象到樣例類外部的話,請只把繼承樹的葉子部分做成樣例類

密封類

  • 使用樣例類做模式匹配的時候,讓編譯器幫你確定已經列出了所有可能的選擇,需要將樣例類的通用超類聲明爲sealed
  • 密封類的所有子類都必須在與該密封類相同的文件中定義
sealed abstract class Amount
case class Dollar(var value:Double) extends Amount
case class Currency(value:Double,unit:String) extends Amount

偏函數

  • 被包在花括號內的一組case語句是一個偏函數partial function,一個並非對所有輸入值都有定義的函數。是PartialFunction[A,B]的一個實例,A是參數類型,B是返回類型,該類有兩個方法,apply方法從匹配到的模式計算函數值,isDefinedAt方法在輸入至少匹配其中一個模式時返回true
val f:PartialFunction[Char,Int] = {case '+' => 1;case '-' => -1}

scala> f('+')
res4: Int = 1

scala> f('-')
res5: Int = -1

scala> f('[')
scala.MatchError: [ (of class java.lang.Character)

scala> f.isDefinedAt('+')
res7: Boolean = true

scala> f.isDefinedAt('1')
res8: Boolean = false

  • 有一些方法接受PartialFunction作爲參數,例如GenTranversable特質的collect方法,將一個偏函數應用到所有在該偏函數有定義的元素,並返回包含這些結果的序列
scala> "-3+4".collect{case '-' => -1;case '+'=>1}
res10: scala.collection.immutable.IndexedSeq[Int] = Vector(-1, 1)
  • 覆蓋所有場景的樣例子句組成的集定義的是一個Function1,而不是一個PartialFunction。map不能用偏函數
scala> "-3+4".map{case '-' => -1;case '+'=>1;case _ => 0}
res12: scala.collection.immutable.IndexedSeq[Int] = Vector(-1, 0, 1, 0)
  • Seq[A]是一個PartialFunction[Int,A],Map[K,V]是一個PartialFunction[K,V],因此可以將映射傳入collect
scala> val names = Array(1,2,3,4)
names: Array[Int] = Array(1, 2, 3, 4)

scala> val scores = Map(1->99,2->97)
scores: scala.collection.immutable.Map[Int,Int] = Map(1 -> 99, 2 -> 97)

scala> names.collect(scores)
res16: Array[Int] = Array(99, 97)
  • lift方法用於將PartialFunction[T,R]變成一個返回類型爲Option[R]的常規函數
  • 使用unlift方法將返回Option[R]的函數變成一個偏函數。
scala> val f:PartialFunction[Char,Int] = {case '+' => 1;case '-' => -1}
f: PartialFunction[Char,Int] = <function1>

scala> val g = f.lift
g: Char => Option[Int] = <function1>

scala> g('+')
res18: Option[Int] = Some(1)

scala> g('0')
res19: Option[Int] = None

  • Regex.replaceSomeIn方法
val varPattern = """\{([0-9]+)\}""".r
val message = "At {1} ,there was {2} on {0}"
val vars = Map("{0}" -> "A","{1}"-> "B","{2}"->"C")
val result = varPattern.replaceSomeIn(message, m => vars.lift(m.matched))
//result: String = At B ,there was C on A
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章