天天看點

Scala中的模式比對

     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在比對上的細節和注意事項

  1. 如果所有case都不比對,那麼會執行case _ 分支,類似于Java中default語句
  2. 如果所有case都不比對,又沒有寫case _ 分支,那麼會抛出MatchError
  3. 每個case中,不用break語句,自動中斷case
  4. 可以在match中使用其它類型,而不僅僅是字元
  5. => 等價于 java swtich 的 :
  6. => 後面的代碼塊到下一個 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)
    }
  }
}
      

  輸出結果

Scala中的模式比對

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)
    }
  }
}
      

  輸出結果為

Scala中的模式比對

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~~")
    }
  }
}
      
Scala中的模式比對

擴充:在Spark中經常會使用到模式比對,如下面在Spark中的一段程式,它使用的就是模式中的變量,分别将值賦給id,name和age,然後再傳入到樣例類Emp中

Scala中的模式比對

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)

  }
}       

   類型比對中注意事項

  1. Map[String, Int] 和Map[Int, String]是兩種不同的類型,其它類推。
  2. 在進行類型比對時,編譯器會預先檢測是否有可能的比對,如果沒有則報錯.
  3. 說明:val result = obj match {  case i : Int => i } “case i : Int => i ”表示 将 i = obj (obj指派給i),然後再判斷類型
  4. 如果 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)
    }
  }
}
      
Scala中的模式比對

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)
    }
  }
}
      

  運作結果

Scala中的模式比對

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)
    }
  }
}
      
Scala中的模式比對

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")
    }
  }
}
      

 對象比對運作機制說明

Scala中的模式比對

執行個體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")
    }
  }
}
      
Scala中的模式比對

 程式執行過程分析

Scala中的模式比對

當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)

  }
}      
Scala中的模式比對

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.樣例類

Scala中的模式比對

 定義三個樣例類

Scala中的模式比對

當我們有一個類型為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
      
Scala中的模式比對

觀察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建立一個與現有對象值相同的新對象,并可以通過帶名參數來修改某些屬性。

提供了一種拷貝對象的方式: 

Scala中的模式比對

 在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)
      

 輸出結果

Scala中的模式比對

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)密封就是不能在其他檔案中定義子類。

Scala中的模式比對
Scala中的模式比對

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比對不生效。