Scala中的模式匹配類似於Java中的switch語法,但是更加強大。 模式匹配語法中,采用match關鍵字聲明,每個分支采用case關鍵字進行聲明,當需要匹配時,會從第一個case分支開始,如果匹配成功,那么執行對應的邏輯代碼,如果匹配不成功,繼續執行下一個分支進行判斷。如果所有case都不匹配,那么會執行case _ 分支,類似於Java中default語句。
1.匹配基本類型
1.觀察如下實例:
object Demo_037 { def main(args: Array[String]): Unit = { val oper = '#' val n1 = 20 val n2 = 10 var res = 0 oper match { case '+' => res = n1 + n2 case '-' => res = n1 - n2 case '*' => res = n1 * n2 case '/' => res = n1 / n2 case _ => println("oper error") } println("res=" + res) } }
match在匹配上的細節和注意事項
- 如果所有case都不匹配,那么會執行case _ 分支,類似於Java中default語句
- 如果所有case都不匹配,又沒有寫case _ 分支,那么會拋出MatchError
- 每個case中,不用break語句,自動中斷case
- 可以在match中使用其它類型,而不僅僅是字符
- => 等價於 java swtich 的 :
- => 后面的代碼塊到下一個 case, 是作為一個整體執行,可以使用{} 擴起來,也可以不擴。
2.如果存在多個默認匹配的情況,則只有一個會生效
object Demo_039 { def main(args: Array[String]): Unit = { for (ch <- "+-3!") { var sign = 0 var digit = 0 ch match { case '+' => sign = 1 case '-' => sign = -1 // 可以有多個默認匹配,但是后面的默認匹配沒有生效,程序也沒報錯 case _ => digit = 3 case _ => sign = 2 } println(ch + " " + sign + " " + digit) } } }
3. 默認匹配放到match的首行時,只會執行默認匹配
object Demo_040 { def main(args: Array[String]): Unit = { for (ch <- "+-3!") { var sign = 0 var digit = 0 ch match { //默認匹配放到match的首行時,后面的匹配不生效 case _ => digit = 3 case '+' => sign = 1 case '-' => sign = -1 } println(ch + " " + sign + " " + digit) } } }
輸出結果
2.守衛
如果想要表達匹配某個范圍的數據,就需要在模式匹配中增加條件守衛
object Demo_038 { def main(args: Array[String]): Unit = { for (ch <- "+-3!") { var sign = 0 var digit = 0 ch match { case '+' => sign = 1 case '-' => sign = -1 //case _ 后出現守衛條件則不表示默認匹配,表示的是忽略所傳入的char case _ if ch.toString.equals("3") => digit = 3 case _ => sign = 2 //默認匹配 } println(ch + " " + sign + " " + digit) } } }
輸出結果為
3.模式中的變量
如果在case關鍵字后跟變量名,那么match前表達式的值會賦給那個變量
object Demo_041 { def main(args: Array[String]): Unit = { val ch = 'V' ch match { case '+' => println("other~") case mychar => println("wise~" + mychar) case _ => println ("say~~") } } }
輸出結果
擴展:在Spark中經常會使用到模式匹配,如下面在Spark中的一段程序,它使用的就是模式中的變量,分別將值賦給id,name和age,然后再傳入到樣例類Emp中
4.類型匹配
可以匹配對象的任意類型,這樣做避免了使用isInstanceOf和asInstanceOf方法
實例:根據輸入的數值,匹配不同類型的數據
object Demo_041 { def main(args: Array[String]): Unit = { // 類型匹配, obj 可能有如下的類型 val a = 7 val obj = if(a == 1) 1 else if(a == 2) "2" else if(a == 3) BigInt(3) else if(a == 4) Map("aa" -> 1) else if(a == 5) Map(1 -> "aa") else if(a == 6) Array(1, 2, 3) else if(a == 7) Array("aa", 1) else if(a == 8) Array("aa") val result = obj match { case a : Int => a case b : Map[String, Int] => "對象是一個字符串-數字的Map集合" case c : Map[Int, String] => "對象是一個數字-字符串的Map集合" case d : Array[String] => "對象是一個字符串數組" case e : Array[Int] => "對象是一個數字數組" case f : BigInt => Int.MaxValue case _ => "啥也不是" } println(result) } }
類型匹配中注意事項
- Map[String, Int] 和Map[Int, String]是兩種不同的類型,其它類推。
- 在進行類型匹配時,編譯器會預先檢測是否有可能的匹配,如果沒有則報錯.
- 說明:val result = obj match { case i : Int => i } “case i : Int => i ”表示 將 i = obj (obj賦值給i),然后再判斷類型
- 如果 case _ 出現在match 中間,則表示隱藏變量名,即不使用,而不是表示默認匹配。
5.匹配數組
1.Array(0) 匹配只有一個元素且為0的數組。
2.Array(x,y) 匹配數組有兩個元素,並將兩個元素賦值為x和y。當然可以依次類推Array(x,y,z) 匹配數組有3個元素的等等....
3.Array(0,_*) 匹配數組以0開始
實例:匹配不同元素的數組
object Demo_042 { def main(args: Array[String]): Unit = { for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))) { val result = arr match { case Array(0) => "0" case Array(x, y) => x + "=" + y case Array(0, _*) => "以0開頭和數組" case _ => "什么集合都不是" } println("result = " + result) } } }
輸出結果
6.匹配列表
實例:匹配各個列表
object Demo_043 { def main(args: Array[String]): Unit = { for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0))) { val result = list match { case 0 :: Nil => "0" //匹配只有一個元素,且元素為0的列表 case x :: y :: Nil => x + " " + y //匹配有兩個元素的列表,列表內元素任意 case 0 :: tail => "0 ..." case _ => "something else" } println(result) } } }
運行結果
7.匹配元組
實例:匹配元組
object Demo_044 { def main(args: Array[String]): Unit = { // 元組匹配 for (pair <- Array((0, 1), (1, 0), (1, 1),(1,0,2))) { val result = pair match { // case (0, _) => "0 ..." // case (y, 0) => y // case _ => "other" //. } println(result) } } }
運行結果
8.對象匹配
對象匹配,什么才算是匹配呢?
規則如下:
- case中對象的unapply方法(對象提取器)返回Some集合則為匹配成功
- 返回none集合則為匹配失敗
實例:求平方根
object Demo_045 { def main(args: Array[String]): Unit = { object Square { def unapply(z: Double): Option[Double] = Some(math.sqrt(z))//如果返回的some中有值,則表示匹配成功,否則匹配失敗 def apply(z: Double): Double = z * z } // 模式匹配使用: val number: Double = Square(36.0) number match { case Square(n) => println(n) case _ => println("nothing matched") } } }
對象匹配運行機制說明
實例2
object Demo_046 { def main(args: Array[String]): Unit = { object Names { def unapplySeq(str: String): Option[Seq[String]] = { if (str.contains(",")) Some(str.split(",")) else None } } val namesString = "Alice,Bob,Thomas" //說明 namesString match { case Names(first, second, third) => { println("the string contains three people's names") // 打印字符串 println(s"$first $second $third") } case _ => println("nothing matched") } } }
運行結果
程序執行過程分析
當case 后面的對象提取器方法的參數為多個,則會默認調用def unapplySeq() 方法
如果unapplySeq返回是Some,獲取其中的值,判斷得到的sequence中的元素的個數是否是三個,如果是三個,則把三個元素分別取出,賦值給first,second和third
其它的規則不變.
9.變量聲明中的模式
match中每一個case都可以單獨提取出來,意思是一樣的
object Demo_047 {
def main(args: Array[String]): Unit = {
val (x, y) = (1, 2)
val (q, r) = BigInt(10) /% 3 //說明 q = BigInt(10) / 3,r = BigInt(10) % 3
val arr = Array(1, 7, 2, 9)
val Array(first, second, _*) = arr // 提出arr的前兩個元素
println(x,y)
println(q,r)
println(first, second)
}
}
輸出結果
10.for表達式中的模式匹配
for循環也可以進行模式匹配
實例:使用模式匹配過濾Map值
object Demo_048 { def main(args: Array[String]): Unit = { val map = Map("A"->1, "B"->0, "C"->3) for ( (k, v) <- map ) { print(k + " -> " + v+"\t") } println() //說明只變量出value=0的(key,value),其他舍棄 for ((k, 0) <- map) { println(k + " --> " + 0) } //說明,等同於上面的,只是使用了守衛 for ((k, v) <- map if v == 0) { println(k + " ---> " + v) } } }
11.樣例類
定義三個樣例類
當我們有一個類型為Amount的對象時,可以用模式匹配來匹配他的類型,並將屬性值綁定到變量(即:把樣例類對象的屬性值提取到某個變量,該功能有用)
實例1
object Demo_049 { def main(args: Array[String]): Unit = { for (amt <- Array(Dollar(1000.0), Currency(1000.0, "RMB"), NoAmount)) { val result = amt match { //說明 case Dollar(v) => "$" + v //說明 case Currency(v, u) => v + " " + u case NoAmount => "" } println(amt + ": " + result) } } } abstract class Amount case class Dollar(value: Double) extends Amount case class Currency(value: Double, unit: String) extends Amount case object NoAmount extends Amount
輸出結果
觀察Currency編譯后的java字節碼
public class Currency extends Amount implements Product, Serializable { private final double value; private final String unit; public static Option<Tuple2<Object, String>> unapply(Currency paramCurrency) { return Currency..MODULE$.unapply(paramCurrency); } public static Currency apply(double paramDouble, String paramString) { return Currency..MODULE$.apply(paramDouble, paramString); } public static Function1<Tuple2<Object, String>, Currency> tupled() { return Currency..MODULE$.tupled(); } public static Function1<Object, Function1<String, Currency>> curried() { return Currency..MODULE$.curried(); } public double value() { return this.value; } public String unit() { return this.unit; } public Currency copy(double value, String unit) { return new Currency(value, unit); } public double copy$default$1() { return value(); } public String copy$default$2() { return unit(); } public String productPrefix() { return "Currency"; } public int productArity() { return 2; } public Object productElement(int x$1) { int i = x$1; switch (i) { default: throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(x$1).toString()); case 1: break; case 0: } return BoxesRunTime.boxToDouble(value()); } public Iterator<Object> productIterator() { return ScalaRunTime..MODULE$.typedProductIterator(this); } public boolean canEqual(Object x$1) { return x$1 instanceof Currency; } public int hashCode() { int i = -889275714; i = Statics.mix(i, Statics.doubleHash(value())); i = Statics.mix(i, Statics.anyHash(unit())); return Statics.finalizeHash(i, 2); } public String toString() { return ScalaRunTime..MODULE$._toString(this); } public boolean equals(Object x$1) { if (this != x$1) { Object localObject = x$1; int i; if ((localObject instanceof Currency)) i = 1; else i = 0; if (i == 0) break label97; Currency localCurrency = (Currency)x$1; if (value() == localCurrency.value()) { str = localCurrency.unit(); String tmp55_45 = unit(); if (tmp55_45 == null) { tmp55_45; if (str == null) break label76; tmpTernaryOp = tmp55_45; break label89; } } } } public Currency(double value, String unit) { Product.class.$init$(this); } }
Currency$.class
public final class Currency$ extends AbstractFunction2<Object, String, Currency> implements Serializable { public static final MODULE$; static { new (); } public final String toString() { return "Currency"; } public Currency apply(double value, String unit) { return new Currency(value, unit); } public Option<Tuple2<Object, String>> unapply(Currency x$0) { return x$0 == null ? None..MODULE$ : new Some(new Tuple2(BoxesRunTime.boxToDouble(x$0.value()), x$0.unit())); } private Object readResolve() { return MODULE$; } private Currency$() { MODULE$ = this; } }
樣例類的copy方法和帶名參數 copy創建一個與現有對象值相同的新對象,並可以通過帶名參數來修改某些屬性。
提供了一種拷貝對象的方式:
在main方法中,添加如下代碼觀察輸出結果
val amt = Currency(29.95, "RMB") val amt1 = amt.copy() //創建了一個新的對象,但是屬性值一樣 val amt2 = amt.copy(value = 19.95) //創建了一個新對象,但是修改了貨幣單位 val amt3 = amt.copy(unit = "英鎊")//.. println(amt) println(amt2) println(amt3)
輸出結果
12.case語句的中置(綴)表達式
什么是中置表達式?1 + 2,這就是一個中置表達式。如果unapply方法產出一個元組,你可以在case語句中使用中置表示法。比如可以匹配一個List序列
List(1, 3, 5, 9) match { //修改並測試 //1.兩個元素間::叫中置表達式,至少first,second兩個匹配才行. //2.first 匹配第一個 second 匹配第二個, rest 匹配剩余部分(5,9) case first :: second :: rest => println(first + second + rest.length) // case _ => println("匹配不到...") }
13.匹配嵌套結構
基本介紹 :操作原理類似於正則表達式
實踐案例-商品捆綁打折出售:
現在有一些商品,請使用Scala設計相關的樣例類,完成商品捆綁打折出售。要求 商品捆綁可以是單個商品,也可以是多個商品。 打折時按照折扣x元進行設計. 能夠統計出所有捆綁商品打折后的最終價格。
創建樣例類
abstract class Item // 項 case class Book(description: String, price: Double) extends Item //Bundle 捆 , discount: Double 折扣 , item: Item* , case class Bundle(description: String, discount: Double, item: Item*) extends Item
匹配嵌套結構(就是Bundle的對象)
//給出案例表示有一捆數,單本漫畫(40-10) +文學作品(兩本書)(80+30-20) = 30 + 90 = 120.0 val sale = Bundle("書籍", 10, Book("漫畫", 40), Bundle("文學作品", 20, Book("《陽關》", 80), Book("《圍城》", 30)))
知識點1-將descr綁定到第一個Book的描述
如何取出 val sale = Bundle("書籍", 10, Book("漫畫", 40), Bundle("文學作品", 20, Book("《陽關》", 80), Book("《圍城》", 30))) 這個嵌套結構中的 "漫畫"
val res = sale match { //如果我們進行對象匹配時,不想接受某些值,則使用_ 忽略即可,_* 表示所有 case Bundle(_, _, Book(desc, _), _*) => desc }
知識點2-通過@表示法將嵌套的值綁定到變量。_*綁定剩余Item到rest
請思考:如何將 "漫畫" 和 val sale = Bundle("書籍", 10, Book("漫畫", 40), Bundle("文學作品", 20, Book("《陽關》", 80), Book("《圍城》", 30))) 這個嵌套結構中的 "漫畫" 和 紫色的部分 綁定到變量,即賦值到變量中.
val result2 = sale match { case Bundle(_, _, art @ Book(_, _), rest @ _*) => (art, rest) } println(result2) println("art =" + result2._1) println("rest=" + result2._2)
知識點3-不使用_*綁定剩余Item到rest
請思考:如何將 "漫畫" 和 紫色部分 val sale = Bundle("書籍", 10, Article("漫畫", 40), Bundle("文學作品", 20, Article("《陽關》", 80), Article("《圍城》", 30))) 這個嵌套結構中的 "漫畫" 和 紫色的部分 綁定到變量,即賦值到變量中.
val result2 = sale match { //說明因為沒有使用 _* 即明確說明沒有多個Bundle,所以返回的rest,就不是WrappedArray了。 case Bundle(_, _, art @ Book(_, _), rest) => (art, rest) } println(result2) println("art =" + result2._1) println("rest=" + result2._2)
實踐案例-商品捆綁打折出售
現在有一些商品,請使用Scala設計相關的樣例類,完成商品可以捆綁打折出售。要求
(1)商品捆綁可以是單個商品,也可以是多個商品。
(2)打折時按照折扣xx元進行設計.
(3)能夠統計出所有捆綁商品打折后的最終價格
object SalesDem0 { def main(args: Array[String]): Unit = { //這里給出了一個具體的打折的案例 // 220. val sale = Bundle("書籍", 10, Book("漫畫", 40), Bundle("文學作品", 20, Book("《陽關》", 80), Book("《圍城》", 30),Book("天龍八部", 100))) //知識點1 - 使用case語句,得到 "漫畫" val res = sale match { //如果我們進行對象匹配時,不想接受某些值,則使用_ 忽略即可,_* 表示所有 case Bundle(_, _, Book(desc, _), _*) => desc } println("res=" + res) // //知識點2-通過@表示法將嵌套的值綁定到變量。_*綁定剩余Item到rest val res2 = sale match { //如果我們進行對象匹配時,不想接受某些值,則使用_ 忽略即可,_* 表示所有 case Bundle(_, _, art @ Book(_, _), rest @ _*) => (art, rest) } println("res2=" + res2) //知識點3-不使用_*綁定剩余Item到rest val res3 = sale match { //如果我們進行對象匹配時,不想接受某些值,則使用_ 忽略即可,_* 表示所有 case Bundle(_, _, art3 @ Book(_, _), rest3) => (art3, rest3) } println("res3=" + res3) //案例的完成 println("price=" + price(sale)) // 120 } def price(it:Item): Double = { it match { case Book(_,p) => p case Bundle(_,disc,its @ _*) => its.map(price).sum - disc } } } //設計樣例類 abstract sealed class Item // 項 case class Book(description: String, price: Double) extends Item case class Food(description: String, price: Double) extends Item //Bundle 捆 , discount: Double 折扣 , item: Item* , case class Bundle(description: String, discount: Double, item: Item*) extends Item
運行結果:
res=漫畫 res2=(Book(漫畫,40.0),WrappedArray(Bundle(文學作品,20.0,WrappedArray(Book(《陽關》,80.0), Book(《圍城》,30.0), Book(天龍八部,100.0))))) res3=(Book(漫畫,40.0),Bundle(文學作品,20.0,WrappedArray(Book(《陽關》,80.0), Book(《圍城》,30.0), Book(天龍八部,100.0)))) price=220.0
14.密封類
(1)如果想讓case類的所有子類都必須在申明該類的相同的源文件中定義,可以將樣例類的通用超類聲明為sealed,這個超類稱之為密封類。
(2)密封就是不能在其他文件中定義子類。
15.scala中的match和java中的switch比較
(1)匹配條件上
java:在switch結構中,case能夠匹配到的類型有四中,分別為:char、byte、short或int
scala:Match的匹配條件有多種,可以是基本數據類型,也可以是引用數據類型(對象,數組,元組,列表等),甚至可以進行類型匹配。
(2)關於默認匹配上的差異
Java:在switch結果中,即便不存在default,也不會報錯,並且default可以置於結構的首行,而不影響執行順序。在switch結構中,defaul只是case都匹配不到時,才執行它。
Scala:在Match中,如果所有case都不匹配,又沒有寫case _ 分支,那么會拋出MatchError;默認匹配的順序,影響case語句的執行順序,當放到match的首行時,后面的case匹配不生效。