zoukankan      html  css  js  c++  java
  • Scala核心编程_第13章 模式匹配

    基本介绍

    Scala中的模式匹配类似于Java中的switch语法,但是更加强大。

    模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。

    如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句。

    Java中的Switch

    // Java
    int i = 1;
    switch ( i ) {
        case 0 :
                break; 
        case 1 : 
                break;
        default : 
                break
    }

    scala中的模式匹配

        val operate = '#'
        val n1 = 20
        val n2 = 10
        var res = 0
        operate match {
          case '+' => res = n1 + n2
          case '-' => res = n1 - n2
          case '*' => res = n1 * n2
          case '/' => res = n1 / n2
          case _ => println("oper error")
        }
        println("res=" + res)
      }
    

    1. 命中case,不用break语句,自动中断case,不再去匹配下面的case。可以在match中使用多种类型,=> 后面的代码块到下一个 case, 是作为一个整体执行,可选用{} 扩起来。
    2. 如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句
    3. 如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError

    守卫

    基本介绍
    如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫

        for (word <- "+-3!") {
          var sign = 0
          var digit = 0
          word match {
            case '+' => sign = 1
            case '-' => sign = -1
            // 说明..
            case _ if word.toString.equals("3") => digit = 3
            case _ => sign = 2
          }
          println("word:"+word + " " + sign + " " + digit)
        }
    

     无论是case_分支还是正常的分支都可以有守卫,守卫的含义是条件不仅要满足case后面的也要满足守卫里面的条件,否则会往下面的case进行匹配。

        for (word <- "+-3!") {
          var sign = 0
          var digit = 0
          word match {
            case '+' => sign = 1
            case '-' if sign!=0 => sign = -1
            case '-' if sign==0 => sign = -10; println("---")
            // 说明..
            case _ if word.toString.equals("3") => digit = 3
            case _ => sign = 2
          }
          println("word:"+word + " " + sign + " " + digit)
        }
    

    模式中的变量

    基本介绍

    如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量。

        for (word <- "+-3!") {
          var sign = 0
          var digit = 0
          word match {
            case '+' => sign = 1
            case '-' if sign!=0 =>sign = -1
            case getVar if word=='3' => println("getVar here ===>:"+getVar)
            // 说明..
            case _ if word.toString.equals("3") => digit = 3
            case _ => sign = 2
          }
          println("word:"+word + " " + sign + " " + digit)
        }
    

      

    类型匹配

    基本介绍

    可以匹配对象的任意类型,这样做避免了使用isInstanceOf和asInstanceOf方法

    应用案例

        while(true){
          val Inputcontent = Console.readInt()
          val arr= Array[Any](1,Map(1->"wqbin"),Array(1,23),1.1)
          val obj=arr(Inputcontent)
          val result = obj match {
            case a : Int => a
            case b : Map[String, Int] => "对象是一个字符串-数字的Map集合"
            case c : Map[Int, String] => "对象是一个数字-字符串的Map集合"
            case e : Map[Any, Any]  => "对象是一个的Map集合"
            case f : Array[String] => "对象是一个字符串数组"
            case g : Array[Int] => "对象是一个数字数组"
            case h : BigInt => Int.MaxValue
            case _ => "啥也不是"
          }
          println("result===>",result)
        }
    

    1. Map[String, Int] 和Map[Int, String]是两种不同的类型,其它类推。
    2. 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错.
    3. val result = obj match {case i : Int => i},这里 case i : Int => i 表示 将 i = obj (其它类推),然后再判断类型
    4. 如果 case _ 出现在match 中间,则表示隐藏变量名,即不使用,而不是表示默认匹配。其实类似于加上了对数据类型的守卫判断。

    类型匹配与参数提取

    以数组为例

        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) => "Array(x, y)==>" + x + "=" + y
            case Array(0, _*) => "以0开头和数组"
            case _ => "其他集合"
          }
          println("result = " + result)
        }
    1. Array(0) 匹配只有一个元素且为0的数组。
    2. Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。当然可以依次类推Array(x,y,z) 匹配数组有3个元素的等等....
    3. Array(0,_*) 匹配数组以0开始

    以列表为例

        for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 1, 0), List(1, 0, 0))) {
          val result = list match {
            case 0 :: Nil => "[0]" //
            case x :: y :: Nil => "["+x + " " + y +"]"
            case 0 :: tail => "[0 ...]" //
            case head :: 0::Nil => "[ ...0]" //
            case _ => "something else"
          }
          println(result)
        }
    

      

     以元祖为例

        for (pair <- Array((0, 1), (1, 0), (1, 1),(1,0,2))) {
          val result = pair match { //
            case (0, _) => "(0 ...)" //
            case (y, 0) => "(... 0)" //
            case (x, y) => "("+x+","+y+")"
            case _ => "other" //.
          }
          println(result)
        }
    

    对象匹配

    基本介绍

    本质是,把match匹配的当参数传入在case过程中的对象的unapply方法(对象提取器)

    1. 返回Some集合则为匹配成功
    2. 返回none集合则为匹配失败
        object Square {
          var score: Double = _
          def unapply(z: Double): Option[Double] = {
            println("unapply被调用 z 是=" + z)
            Some(math.sqrt(z))
            //    None
          }

    说明

    1. unapply方法是对象提取器
    2. 接收z:Double 类型
    3. 返回类型是Option[Double]
    4. 返回的值是 Some(math.sqrt(z)) 返回z的开平方的值,并放入到Some(x)
        // 模式匹配使用:
        val number: Double = Square(6.0)// 36.0 //调用apply
    
        number match {
    
          case Square(n) => println("匹配成功 n=" + n)
          case _ => println("nothing matched")
        }

    说明 case Square(n) 的运行的机制

    1. 当匹配到 case Square(n)
    2. 调用Square 的 unapply(z: Double),z 的值就是 number
    3. 如果对象提取器 unapply(z: Double) 返回值调用 def isEmpty: Boolean,则False表示匹配成功,同时将6 赋给 Square(n) 的n
    4. 如果对象提取器 unapply(z: Double) 返回的是None ,则表示匹配不成功

    总结:

    1. 构建对象时apply会被调用 ,比如 val n1 = Square(5)
    2. 当将 Square(n) 写在 case 后时[case Square(n) => xxx],会默认调用unapply 方法(对象提取器)
    3. number 会被 传递给def unapply(z: Double) 的 z 形参
    4. 如果返回的是Some集合,则unapply提取器返回的结果会返回给 n 这个形参
    5. case中对象的unapply方法(提取器)返回some集合则为匹配成功
    6. 返回none集合则为匹配失败
        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)
          // 1. 会调用 unapplySeq(str),把 "Alice,Bob,Thomas" 传入给 str
          // 2. 如果 返回的是 Some("Alice","Bob","Thomas"),分别给 (first, second, third)注意,这里的返回的值的个数需要和 (first, second, third)要一样
          // 3. 如果返回的None ,表示匹配失败
    
          case Names(first, _, second) =>
            println("the string contains three people's names:"+s"$first $second")
          case _ => println("nothing matched")
        }

      

     Option是啥? 

    说明:我感觉这篇博客写的非常好。

    变量声明中与for循环中的模式匹配

    变量声明中

    match中每一个case都可以单独提取出来,意思是一样的.

    应用案例

    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(first, second)

    for循环中的

        val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
        for ((k, v) <- map) {
          println(k + " -> " + v)
        }
        //说明
        for ((k, 0) <- map) {
          println(k + " --> " + 0)
        }
        //说明
        for ((k, v) <- map if v == 0) {
          println(k + " ---> " + v)
        }
    

     样例类

    case类在模式匹配和actor中经常使用到,当一个类被定义成为case类后,Scala会自动帮你创建一个伴生对象并帮你实现了一系列方法,如下:

    scala> case class GoodStudent(name: String, score: Int){}
    defined class GoodStudent
    

    他底层的代码逻辑如下:

    对object类型增加了一些方法。
    作用如下:
    1.实现了apply方法,意味着你不需要使用new关键字就能创建该类对象
    scala> var st=GoodStudent("wqbin",100)
    st: GoodStudent = GoodStudent(wqbin,100)
    

    2.实现了unapply方法,可以通过模式匹配来获取类属性,是Scala中抽取器的实现和模式匹配的关键方法。

    scala> st match{case GoodStudent(x,y)=>println(x,y)}
    (wqbin,100)
    

    3.实现了类构造参数的getter方法(构造参数默认被声明为val),但是当你构造参数是声明为var类型的,它将帮你实现setter和getter方法(不建议将构造参数声明为var)

    构造参数为val的情况(默认):
    scala> st.name
    res1: String = wqbin
    
    scala> st.name="wang"
    <console>:14: error: reassignment to val
           st.name="wang"
    

    构造参数为var的情况:

    scala> case class GoodStudent(var name: String, score: Int){}
    defined class GoodStudent
    
    scala> var st=GoodStudent("wqbin",100)
    st: GoodStudent = GoodStudent(wqbin,100)
    
    scala> st.name="wang"
    st.name: String = wang
    
    scala> st
    res2: GoodStudent = GoodStudent(wang,100)
    

    4.样例类的copy方法和带名参数

    copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。

    scala> val st1=st.copy()
    st1: GoodStudent = GoodStudent(wang,100)
    
    scala> st1.hashCode()
    res3: Int = -605011778
    
    scala> st.hashCode()
    res4: Int = -605011778
    
    scala> val st2=st.copy("wqbin",120)
    st2: GoodStudent = GoodStudent(wqbin,120)
    
    scala> st2.hashCode()
    res5: Int = -1824578579
    

    好像命名我们进行了copy,st1与st的hashcode内存地址是不同的但是st1与st哈希码值是一样,是因为case样例类重写了hashcode方法。

     5.样例类的也重写了equals和toString方法

    scala> GoodStudent("wqbin",100) ==GoodStudent("wqbin",100)
    Boolean = true
    
    scala> st1==st2
    Boolean = false
    
    scala> st1==st
    Boolean = true
    
    scala> st.toString()
    String = GoodStudent(wang,100)
    

    中置表达式

    基本介绍

    什么是中置表达式?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("匹配不到...")
        }
    

    底层代码:

        Predef..MODULE$.println("hello~~");
        List localList1 = List..MODULE$.apply(Predef..MODULE$.wrapIntArray(new int[] { 1, 3, 5, 9 }));
        if ((localList1 instanceof .colon.colon))
        {
          .colon.colon localcolon1 = (.colon.colon)localList1;int first = BoxesRunTime.unboxToInt(localcolon1.head());List localList2 = localcolon1.tl$1();
          if ((localList2 instanceof .colon.colon))
          {
            .colon.colon localcolon2 = (.colon.colon)localList2;int second = BoxesRunTime.unboxToInt(localcolon2.head());List rest = localcolon2.tl$1();Predef..MODULE$.println(BoxesRunTime.boxToInteger(first + second + rest.length()));localBoxedUnit = BoxedUnit.UNIT; return;
          }
        }
        Predef..MODULE$.println("��������...");BoxedUnit localBoxedUnit = BoxedUnit.UNIT;
    
    

    匹配嵌套结构

    scala> abstract class Item
    defined class Item
    
    scala> case class Book(description: String, price: Double) extends Item
    defined class Book
    
    scala> case class Bundle(description: String, discount: Double, item: Item*) extends Item
    defined class Bundle
    

    表示有一捆数,单本漫画(40-10) +文学作品(两本书)(80+30-20) = 30 + 90 = 120.0

    scala> val sale = Bundle("书籍", 10,  Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))
    sale: Bundle = Bundle(书籍,10.0,WrappedArray(Book(漫画,40.0), Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))))
    
    • 知识点1-将desc绑定到第一个Book的描述

     分析就是要取出嵌套结构中的 "漫画"

     如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有

    scala> val res = sale match  {case Bundle(_, _, Book(desc, _), _*) => desc}
    res: String = 漫画
    • 知识点2-通过@表示法将嵌套的值绑定到变量

    把 "漫画" 和 boundle(文学作品...) 绑定到一个变量上,即赋值到变量中.

    scala> val result2 = sale match {case Bundle(_, _, cartoon @ Book(_, _), rest @ _*) => (cartoon,rest)}
    result2: (Book, Seq[Item]) = (Book(漫画,40.0),WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book( 《围城》,30.0)))))
    
    scala> println(result2)
    (Book(漫画,40.0),WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))))
    
    scala> println("art =" + result2._1)
    art =Book(漫画,40.0)
    
    scala> println("rest=" + result2._2)
    rest=WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))
    • 知识点3-不使用_*绑定剩余Item到rest

    因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是WrappedArray了。

    scala> val result2 = sale match {case Bundle(_, _, art @ Book(_, _), rest) => (art, rest)}
    result2: (Book, Item) = (Book(漫画,40.0),Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))
    
    scala> println(result2)
    (Book(漫画,40.0),Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))
    
    scala> println("art =" + result2._1)
    art =Book(漫画,40.0)
    
    scala> println("rest=" + result2._2)
    rest=Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))
    
    • 知识点4-递归调用

    请计算sale的销售总额

    scala>   def price(it:Item): Double = {
         |     it match  {
         |       case Book(_,p) => p
         |       case Bundle(_,disc,its @ _*) => its.map(price).sum - disc
         |     }
         |   }
    price: (it: Item)Double
    
    scala> println("price=" + price(sale))
    price=220.0
    

    密封类

    基本介绍

    如果想让case类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明为sealed,这个超类称之为密封类。

    密封就是不能在其他文件中定义子类。

  • 相关阅读:
    C#下给数字前面补0的方法
    Notepad++ xml 文件不能语法着色的问题解决
    excel 技巧
    编译时报警 implicit declaration of function
    配置ASP.NET平台时遇到的“访问IIS元数据库失败”解决方案
    用360安全卫士批量本地快速给系统打补丁【转贴】
    新雨情系统随笔
    我的开发博客开通了
    JQuery资料
    IGNORE_DUP_KEY = OF的作用
  • 原文地址:https://www.cnblogs.com/wqbin/p/13132202.html
Copyright © 2011-2022 走看看