美文网首页
scala(二十一) 模式匹配(match)

scala(二十一) 模式匹配(match)

作者: 万事万物 | 来源:发表于2021-07-05 07:08 被阅读0次

    前言

    Scala中的模式匹配类似于Java中的switch语法,但是更加强大。
    模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句。

    语法:

    val 返回值 变量 match{
        case <条件> => {匹配上的表达式}
        case <条件> => {匹配上的表达式}
        ...
        case _ => {类似于Java中default语句}
    }

    案例演示

    案例一演示

    模拟 菜单选项 输入校验

      def main(args: Array[String]): Unit = {
    
        val context =
          """
            |欢迎来到 xxx图书馆管理系统
            |1. 借书
            |2. 还书
            |3. 查看库存
            |""".stripMargin
    
        println(context)
        print("请输入操作选项:")
        val str: String = StdIn.readLine()
    
        str match {
          case "1" => println("给你一本书")
          case "2" => println("还书已完成")
          case "3" => println("还有19000本书")
          case _ => println("暂时不支持该功能")
        }
      }
    

    借书

    
    欢迎来到 xxx图书馆管理系统
    1. 借书
    2. 还书
    3. 查看库存
    
    请输入操作选项:1
    给你一本书
    

    其他操作

    欢迎来到 xxx图书馆管理系统
    1. 借书
    2. 还书
    3. 查看库存
    
    请输入操作选项:4
    暂时不支持该功能
    

    注意:若不指定 case _,程序若是匹配不上,那么将抛出异常

     str match {
          case "1" => println("给你一本书")
          case "2" => println("还书已完成")
          case "3" => println("还有19000本书")
        }
    
    欢迎来到 xxx图书馆管理系统
    1. 借书
    2. 还书
    3. 查看库存
    
    请输入操作选项:4
    Exception in thread "main" scala.MatchError: 4 (of class java.lang.String)
        at com.admin.xxx.collection.Match$.main(Match.scala:22)
        at com.admin.xxx.collection.Match.main(Match.scala)
    

    模式匹配一旦匹配到条件之后,执行完条件后面的块表达式之后会自动退出

    模式匹配一般在最后会加上一个case x/case _ 用于匹配其他情况

    案例二演示

    上超市购物,通过商品名称匹配,返回对应商品的单价

      def main(args: Array[String]): Unit = {
    
        print("请输入商品名称:")
        val str: String = StdIn.readLine()
    
        val price= str match {
          case "可乐" => 3.0
          case "鸡翅" => 9.8
          case "衣服" => 56.7
          case _ => println("暂无此商品")
        }
    
        println(s"商品单价 $price")
    
      }
    

    测试1

    请输入商品名称:可乐
    商品单价 3.0
    

    测试2

    请输入商品名称:薯片
    暂无此商品
    商品单价 ()
    

    模式匹配有返回值,返回值就是符合条件的分支的块表达式的结果值

    通过上面两个案例说明了 模式匹配的基本用法,接下来看看模式匹配的高阶应用。


    模式守卫

    类似与 for 中的守卫,可以用于做一些条件过滤。

    语法:

    模式匹配守卫:
    变量名 match {
        case 条件 if (布尔表达式) => ...
        case 条件 if (布尔表达式) => ...
        case 条件 if (布尔表达式) => ...
        ...
    }

    案例:校验用户密码;

    1. 首先要满足 8位及以上长度
    2. 不能为纯数字
    3. 不能为纯字母
      def main(args: Array[String]): Unit = {
    
    
        print("请输入你的密码:")
        val str: String = StdIn.readLine()
    
        str match {
          case _ if str.length <8 => println("密码长度不够")
          case _ if "[0-9]*".r.pattern.matcher(str).matches   => println("不能全为数字 ")
          case _ if "[a-zA-Z]*".r.pattern.matcher(str).matches   => println("不能全为字母")
          case pass => println(s"密码符合:$pass")
        }
    
      }
    

    长度校验

    请输入你的密码:13ffd11
    密码长度不够
    

    数字校验

    请输入你的密码:2222414134132
    不能全为数字 
    

    字母校验

    请输入你的密码:afafasfasdfas
    不能全为字母
    

    混合输入

    请输入你的密码:123abc123
    密码符合:123abc123
    

    使用模式匹配时,必须指定条件。;如:

     case pass => println(s"密码符合:$pass")
    

    => 未用到条件时,可以将 参数定义成_;如:

    case _ if str.length <8 => println("密码长度不够")
    

    类型匹配

    匹配常量

    scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。

      def main(args: Array[String]): Unit = {
    
        def matchConstant(x:Any): Unit ={
          x match {
            case 1 => println("数字1")
            case 0.0 => println("浮点数0.0")
            case "hello" => println("操作符hello")
            case '+' => println("Char +")
            case true => println("布尔类型 true")
          }
        }
      }
    

    数字匹配

    matchConstant(1)
    数字1
    

    浮点数匹配

    matchConstant(0.0)
    浮点数0.0
    

    字符串匹配

    matchConstant("hello")
    操作符hello
    

    字符匹配

    matchConstant('+')
    Char +
    

    布尔匹配

    matchConstant(true)
    布尔类型 true
    

    匹配外部变量

      def main(args: Array[String]): Unit = {
    
        def matchConstant(x:Any): Unit ={
          val Name=1
          x match {
            case Name=> println("数字1")
          }
        }
      }
    

    匹配数字1

    matchConstant(1)
    数字1
    

    更改代码,加个 x

      def main(args: Array[String]): Unit = {
    
        def matchConstant(x:Any): Unit ={
          val Name=1
          x match {
            case Name => println(s"Name=${Name}")
            case x=> println(s"x=${x}")
          }
        }
      }
    

    测试,输入2 匹配的是 x ,🆗,没问题。

    matchConstant(2)
    x=2
    

    再更改代码;将case Name 改成小写 name

        def matchConstant(x:Any): Unit ={
          val Name=1.1
          x match {
            case name => println(s"Name=$name")
            case x=> println(s"x=${x}")
          }
        }
    

    测试

     matchConstant(2)
    Name=2
    

    如果模式匹配中需要使用外部变量作为匹配的条件,此时需要变量名首字母大写


    匹配类型

    需要进行类型判断时,可以使用isInstanceOf[T]和asInstanceOf[T],也可使用模式匹配实现同样的功能。

    语法:

    变量名 match {
        case x: 类型 => ...
        case _: 类型 => ...
        ...
    }

      def main(args: Array[String]): Unit = {
    
        def matchConstant(x:Any): Unit ={
          x match {
            case name:Int => println(s"这是Int类型")
            case name:String=> println(s"这是String类型")
            case name:Double=> println(s"这是Double类型")
            case name:Boolean=> println(s"这是Boolean类型")
          }
        }
      }
    

    匹配String类型

    matchConstant("hello")
    这是String类型
    

    匹配Int类型

    matchConstant(123)
    这是Int类型
    

    匹配Boolean类型

    matchConstant(false)
    这是Boolean类型
    

    匹配数组

    scala模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为0的数组。

    案例:

      def main(args: Array[String]): Unit = {
    
    
        val arr=Array[Any]("hello",123)
    
        arr match {
          case Array(x,y) => println(s"参数只能包含两个; 数据值:$x,$y")
          case _ if arr.length>5 => println("数组长度必须大于5个元素")
          case Array(x:Int,y:String,z:Double) => println("参数只能包含两个; 并且 x为Int类型,y为String类型,z为Double类型")
          case Array(x,y,_*) => println(s"参数至少包含两个; 数据值:$x,$y")
          case _ => println("数组其中格式匹配")
        }
      }
    

    长度必须大于五个

    val arr=Array[Any]("hello",123,2,3.4,3,'a')
    数组长度必须大于5个元素
    

    参数类型匹配

    val arr=Array[Any](1,"hello",3.4)
    参数只能包含两个; 并且 x为Int类型,y为String类型,z为Double类型
    

    其他的就不试了


    匹配List

    第一种方式:和数组的一样

      def main(args: Array[String]): Unit = {
    
        val list = List[Any](1,5,8,2,10)
    
        //第一种匹配方式
        list match {
          case List(x) => println("list中只有一个元素")
          case List(x:Int,y:String) => println("list中有两个元素")
          case List(x,_*) => println("list中至少有一个元素")
        }
    
        //第二种匹配方式
        list match {
          case x :: Nil => println("list中只有一个元素")
          case x :: y :: Nil => println("list中有两个元素")
          case (x:String) :: y :: tail =>  println(s"String list中至少有一个元素: ${x} ${tail}" )
          case (x:Int) :: y :: tail =>  println(s"Int list中至少有一个元素: ${x} ${tail}" )
    
        }
      }
    

    第二种方式:刚方式

      def main(args: Array[String]): Unit = {
    
        val list = List[Any](1,5,8,2,10)
    
        //第二种匹配方式
        list match {
          case x :: Nil => println("list中只有一个元素")
          case x :: y :: Nil => println("list中有两个元素")
          case (x:String) :: y :: tail =>  println(s"String list中至少有一个元素: ${x} ${tail}" )
          case (x:Int) :: y :: tail =>  println(s"Int list中至少有一个元素: ${x} ${tail}" )
        }
      }
    

    tail :表示剩下的元素;除去 x 和 y 对应的元素外,剩下的都是tail

    def main(args: Array[String]): Unit = {
    
        val list = List[Any](1,5,8,2,10)
    
        //第二种匹配方式
        list match {
          case (x:Int) :: y :: tail =>  println(s"Int list中至少有一个元素: ${x} ${y} ${tail}" )
        }
    }
    
    Int list中至少有一个元素: 1 5 List(8, 2, 10)
    

    这里的 tail 只是取个名而已,实际上叫啥都可以

    def main(args: Array[String]): Unit = {
    
        val list = List[Any](1,5,8,2,10)
    
        //第二种匹配方式
        list match {
          case (x:Int) :: y :: aa=>  println(s"Int list中至少有一个元素: ${x} ${y} ${aa}" )
        }
    }
    
    Int list中至少有一个元素: 1 5 List(8, 2, 10)
    

    注意:类型匹配需要带();如

    case (x:String) :: y :: tail =>  println(s"String list中至少有一个元素: ${x} ${tail}" )
    

    匹配元组

      def main(args: Array[String]): Unit = {
    
        val tuple =("zhangsan",18,"北京朝阳区")
    
        tuple match {
          case (x,y,z) => println(s"${x},${y},${z}")
        }
      }
    
    zhangsan,18,北京朝阳区
    

    匹配元组的时候,变量是几元元组匹配条件中就必须是几元元组

    匹配元组的应用场景
    有一批数据,元组套元组,不知道是多少层。

     val tlist: List[(String, (String, (String, (String, Int))))] = List(
          ("宝安区1",("宝安中学1",("王者班1",("王昭君1",201)))),
          ("宝安区2",("宝安中学2",("王者班2",("王昭君2",202)))),
          ("宝安区3",("宝安中学3",("王者班3",("王昭君3",203)))),
          ("宝安区4",("宝安中学4",("王者班4",("王昭君4",204))))
    )
    

    现在以后需要,获取各个班中的学生名称(王昭君N)

    普通的方式。

    def main(args: Array[String]): Unit = {
    
        val tlist: List[(String, (String, (String, (String, Int))))] = List(
          ("宝安区1",("宝安中学1",("王者班1",("王昭君1",201)))),
          ("宝安区2",("宝安中学2",("王者班2",("王昭君2",202)))),
          ("宝安区3",("宝安中学3",("王者班3",("王昭君3",203)))),
          ("宝安区4",("宝安中学4",("王者班4",("王昭君4",204))))
        )
    
        for(e <- tlist){
          val value: String = e._2._2._2._1
          println(value)
        }
      }
    

    输出结果

    王昭君1
    王昭君2
    王昭君3
    王昭君4
    

    为了获取里面的数据,需要写成这样的形式e._2._2._2._1;开发时也许还知道各个._2 是什么,但是过一段时间,可能就忘了,此种方式出现的问题就是可读性极差。

       def readStuName(tlist:List[(String, (String, (String, (String, Int))))] ): Unit ={
         for(e <- tlist){
           val value: String = e._2._2._2._1
           println(value)
         }
       }
    

    若该list 是别人传给我们的,不看原数据,更不明白是什么了。

    模式匹配的方式

      def main(args: Array[String]): Unit = {
    
       def readStuName(tlist:List[(String, (String, (String, (String, Int))))] ): Unit ={
         for(e <- tlist){
           e match {
             case (area,(school,(clazz,(stuName,id)))) => println(stuName)
           }
         }
       }
    
        val tlist: List[(String, (String, (String, (String, Int))))] = List(
          ("宝安区1",("宝安中学1",("王者班1",("王昭君1",201)))),
          ("宝安区2",("宝安中学2",("王者班2",("王昭君2",202)))),
          ("宝安区3",("宝安中学3",("王者班3",("王昭君3",203)))),
          ("宝安区4",("宝安中学4",("王者班4",("王昭君4",204))))
        )
    
        readStuName(tlist)
      }
    

    结果

    王昭君1
    王昭君2
    王昭君3
    王昭君4
    

    同样获取结果,采用模式匹配的方式,可读性大大提高

       def readStuName(tlist:List[(String, (String, (String, (String, Int))))] ): Unit ={
         for(e <- tlist){
           e match {
             case (area,(school,(clazz,(stuName,id)))) => println(stuName)
           }
         }
       }
    

    即使没有源数据,也能明白各个字段的意思;
    当然获取数据的方式也更加方便;比如获取学生的姓名及所在的学校

    def readStuName(tlist:List[(String, (String, (String, (String, Int))))] ): Unit ={
         for(e <- tlist){
           e match {
             case (area,(school,(clazz,(stuName,id)))) => println(s"姓名:${stuName} 学校:${school}")
           }
         }
       }
    

    结果

    姓名:王昭君1 学校:宝安中学1
    姓名:王昭君2 学校:宝安中学2
    姓名:王昭君3 学校:宝安中学3
    姓名:王昭君4 学校:宝安中学4
    

    如果解决上面的太复杂了,还可以进行简写

    def readStuName(tlist:List[(String, (String, (String, (String, Int))))] ): Unit ={
         tlist.foreach({case (area,(school,(clazz,(stuName,id)))) => println(s"姓名:${stuName} 学校:${school}")})
    }
    

    然后在进行简化 不要();直接改成这样。

    tlist.foreach{case (area,(school,(clazz,(stuName,id)))) => println(s"姓名:${stuName} 学校:${school}")}
    

    匹配对象和样例类

    样例类: 其实就是伴生类和伴生对象的封装

    语法:

    case class 类名([val/var]属性名:类型,...)

    定义一个类:

    class Person(val name:String,val age:Int,val sex:Char)
    

    这是一个普通类,若要定义成样例类,需要加上 case

    case class Person(val name:String,val age:Int,val sex:Char)
    

    获取样例类的对象;通过 apply

    Person.apply("张三",18,'男')
    

    apply 可以进行省略;所以可以写成下面这种方式。

    Person("张三",18,'男')
    

    获取样例对象: Person.apply(值,...) / Person(值,...)

    获取样例类数据

    println(person.name) // 张三
    println(person.age) // 18
    println(person.sex) // 男
    

    样例类中属性不用val/var修饰的时候,默认就是val修饰;使用 val修饰的属性不能进行修改。

    使用样例类进行模式匹配

    def main(args: Array[String]): Unit = {
        val person=Person("张三",18,'男')
    
        person match {
          case Person(x,y,z) => println(s"姓名:$x;年龄:$y;性别:$z")
        }
    }
    
    姓名:张三;年龄:18;性别:男
    

    普通类可以进行模式匹配吗?
    我们试一试;定义一个Student类

    class Student(val name:String,val age:Int,val sex:Char)
    

    创建对象

    val student=new Student("李四",20,'男')
    

    进行模式匹配 ;提醒我们报错了

    Cannot resolve method student.unapply
    Cannot resolve symbol student
    

    普通类不能直接用于模式匹配,如果想要让普通类用于模式匹配必须在伴生对象中定义unapply方法

    定义一个 Student 伴生对象;实现 unapply

      object  Student{
        def unapply(arg: Student): Option[(String, Int, Char)] = {
          if(arg == null) None
          else Some((arg.name,arg.age,arg.sex))
        }
      }
    

    此时 普通类就可以实现模式匹配了

    student match {
       case Student(x,y,z) => println(s"姓名:$x;年龄:$y;性别:$z")
    }
    
    姓名:李四;年龄:20;性别:男
    

    变量声明,for循环模式匹配

    定义一个元组

    val t =("张三",18)
    

    若要取值的话需要使用 ._n 的方式。

    val t =("张三",18)
    println(t._1)
    println(t._2)
    

    其实可以换种方式

    val (name,age) =("张三",18)
    println(name)
    println(age)
    

    当然也可以用再List

    val List(x,y,z)=List(1,2,3)
    println(x,y,z)
    

    对象也是可以的。

    val Person(name,age,sex)=Person("张三",18,'男')
    println(name,age,sex)
    

    除了这些,数组,set, map 等可以用变量声明的方式简化模式匹配,此种方式类似于(如下)方式。

    val person=Person("张三",18,'男')
    person match {
      case Person(name,age,sex) => println(s"$name,$age,$sex")
    }
    

    虽然用的是Person做案例,其他类型都是一样。

    说完变量声明;再说说for循环模式

    定义一个map

    val map=Map("name"-> "张三","age"->18,"sex"->'男')
    

    想这种很方便的键值对方式,使用模式匹配拿数据就很简单了。

    for((k,v)<-map){
      println(s"$k = $v")
    }
    
    name = 张三
    age = 18
    sex = 男
    

    对象亦是如此

      def main(args: Array[String]): Unit = {
        val person1=Person("张三",18,'男')
        val person2=Person("张三",18,'男')
        val person3=Person("张三",18,'男')
        val person4=Person("张三",18,'男')
    
        val personList=List(person1,person2,person3,person4)
    
        for(Person(name,age,sex)<-personList){
          println(s"$name, $age,$sex")
        }
      }
    

    虽然内容一样,只是我比较懒,难道改了(copy 很香),但都属于不同的对象。

    张三, 18,男
    张三, 18,男
    张三, 18,男
    张三, 18,男
    

    偏函数中的模式匹配

    什么叫偏函数

    偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为List[Int],而我们需要的是第一个元素是0的集合,这就是通过模式匹配实现的。

    偏函数定义

    val second: PartialFunction[List[Int], Option[Int]] = {
    case x :: y :: _ => Some(y)
    }


    偏函数格式说明

    注:该偏函数的功能是返回输入的List集合的第二个元素
    偏函数: 没有match关键字的模式匹配称之为偏函数

    案例:从元组集合中获取 学生姓名及学校(和上面的案例一样)

        val list: List[(String, (String, (String, (String, Int))))] = List(
          ("宝安区1",("宝安中学1",("王者班1",("王昭君1",201)))),
          ("宝安区2",("宝安中学2",("王者班2",("王昭君2",202)))),
          ("宝安区3",("宝安中学3",("王者班3",("王昭君3",203)))),
          ("宝安区4",("宝安中学4",("王者班4",("王昭君4",204))))
        )
    

    使用模式匹配的方式获取

    list.foreach({
          case (area,(school,(clazz,(stuName,id)))) => println(s"姓名:$stuName,学校:$school")
    })
    

    因为只有一句表达式;()可以进行省略。

    list.foreach{case (area,(school,(clazz,(stuName,id)))) => println(s"姓名:$stuName,学校:$school")}
    
    姓名:王昭君1,学校:宝安中学1
    姓名:王昭君2,学校:宝安中学2
    姓名:王昭君3,学校:宝安中学3
    姓名:王昭君4,学校:宝安中学4
    

    使用偏函数的方式

    val fun1:PartialFunction[(String,(String,(String,(String,Int)))),Unit]={
      case (area,(school,(clazz,(stuName,id)))) => println(s"姓名:$stuName,学校:$school")
    }
    
    // 调用
    list.foreach(fun1)
    

    fun1:函数名称
    (String,(String,(String,(String,Int)))) : 这个只是传入的参数类型;就是集合单个元组中的类型。
    Unit :表示返回值类型,因为这里只是打印所有不需返回

    姓名:王昭君1,学校:宝安中学1
    姓名:王昭君2,学校:宝安中学2
    姓名:王昭君3,学校:宝安中学3
    姓名:王昭君4,学校:宝安中学4
    

    带返回的偏函数;不在偏函数中进行打印

    val fun1:PartialFunction[(String,(String,(String,(String,Int)))),String]={
      case (area,(school,(clazz,(stuName,id)))) => s"姓名:$stuName,学校:$school"
    }
    // 调用并打印
    list.foreach(e=> println(fun1(e)))
    

    这里不能简写这样如下;因为需要打印,无法嵌套传参,必须要明确指定传参。

    list.foreach(println(fun1))
    

    目前案例比较简单,可能从视觉上来说,第一种的模式匹配的方式,看起来比较简洁。偏函数需要定义一个函数(包裹模式匹配定义);所以觉得特麻烦。若业务复杂起来,往往偏函数的方式更加合理。具体的原因:函数就是比较好,真正调用时,这样的代码(如下)还不好吗?

    // 调用
    list.foreach(fun1)
    

    除了foreach 在很多地方都可以用到偏函数;如map

    val newList: List[String] = list.map(e => fun1(e))
    
    // 打印
    println(newList.mkString(","))
    
    姓名:王昭君1,学校:宝安中学1,姓名:王昭君2,学校:宝安中学2,姓名:王昭君3,学校:宝安中学3,姓名:王昭君4,学校:宝安中学4
    

    最后:

    关于模式匹配的知识到这里也就完了,有什么疑问或者我没有补充到的,欢迎下方探讨。

    相关文章

      网友评论

          本文标题:scala(二十一) 模式匹配(match)

          本文链接:https://www.haomeiwen.com/subject/ecwayltx.html