前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >scala(二十一) 模式匹配(match)

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

作者头像
用户1483438
发布2022-04-22 16:29:45
7180
发布2022-04-22 16:29:45
举报
文章被收录于专栏:大数据共享

前言

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

语法:

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

案例演示

案例一演示

模拟 菜单选项 输入校验

代码语言:javascript
复制
  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("暂时不支持该功能")
    }
  }

借书

代码语言:javascript
复制
欢迎来到 xxx图书馆管理系统
1. 借书
2. 还书
3. 查看库存

请输入操作选项:1
给你一本书

其他操作

代码语言:javascript
复制
欢迎来到 xxx图书馆管理系统
1. 借书
2. 还书
3. 查看库存

请输入操作选项:4
暂时不支持该功能

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

代码语言:javascript
复制
 str match {
      case "1" => println("给你一本书")
      case "2" => println("还书已完成")
      case "3" => println("还有19000本书")
    }
代码语言:javascript
复制
欢迎来到 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 _ 用于匹配其他情况

案例二演示

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

代码语言:javascript
复制
  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

代码语言:javascript
复制
请输入商品名称:可乐
商品单价 3.0

测试2

代码语言:javascript
复制
请输入商品名称:薯片
暂无此商品
商品单价 ()

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

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

模式守卫

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

语法:

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

案例:校验用户密码;

  1. 首先要满足 8位及以上长度
  2. 不能为纯数字
  3. 不能为纯字母
代码语言:javascript
复制
  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")
    }

  }

长度校验

代码语言:javascript
复制
请输入你的密码:13ffd11
密码长度不够

数字校验

代码语言:javascript
复制
请输入你的密码:2222414134132
不能全为数字 

字母校验

代码语言:javascript
复制
请输入你的密码:afafasfasdfas
不能全为字母

混合输入

代码语言:javascript
复制
请输入你的密码:123abc123
密码符合:123abc123

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

代码语言:javascript
复制
 case pass => println(s"密码符合:$pass")

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

代码语言:javascript
复制
case _ if str.length <8 => println("密码长度不够")

类型匹配

匹配常量

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

代码语言:javascript
复制
  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")
      }
    }
  }

数字匹配

代码语言:javascript
复制
matchConstant(1)
数字1

浮点数匹配

代码语言:javascript
复制
matchConstant(0.0)
浮点数0.0

字符串匹配

代码语言:javascript
复制
matchConstant("hello")
操作符hello

字符匹配

代码语言:javascript
复制
matchConstant('+')
Char +

布尔匹配

代码语言:javascript
复制
matchConstant(true)
布尔类型 true

匹配外部变量

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {

    def matchConstant(x:Any): Unit ={
      val Name=1
      x match {
        case Name=> println("数字1")
      }
    }
  }

匹配数字1

代码语言:javascript
复制
matchConstant(1)
数字1

更改代码,加个 x

代码语言:javascript
复制
  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 ,🆗,没问题。

代码语言:javascript
复制
matchConstant(2)
x=2

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

代码语言:javascript
复制
    def matchConstant(x:Any): Unit ={
      val Name=1.1
      x match {
        case name => println(s"Name=$name")
        case x=> println(s"x=${x}")
      }
    }

测试

代码语言:javascript
复制
 matchConstant(2)
Name=2

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


匹配类型

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

语法:

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

代码语言:javascript
复制
  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类型

代码语言:javascript
复制
matchConstant("hello")
这是String类型

匹配Int类型

代码语言:javascript
复制
matchConstant(123)
这是Int类型

匹配Boolean类型

代码语言:javascript
复制
matchConstant(false)
这是Boolean类型

匹配数组

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

案例:

代码语言:javascript
复制
  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("数组其中格式匹配")
    }
  }

长度必须大于五个

代码语言:javascript
复制
val arr=Array[Any]("hello",123,2,3.4,3,'a')
数组长度必须大于5个元素

参数类型匹配

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

其他的就不试了

匹配List

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

代码语言:javascript
复制
  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}" )

    }
  }

第二种方式:刚方式

代码语言:javascript
复制
  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

代码语言:javascript
复制
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}" )
    }
}
代码语言:javascript
复制
Int list中至少有一个元素: 1 5 List(8, 2, 10)

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

代码语言:javascript
复制
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}" )
    }
}
代码语言:javascript
复制
Int list中至少有一个元素: 1 5 List(8, 2, 10)

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

代码语言:javascript
复制
case (x:String) :: y :: tail =>  println(s"String list中至少有一个元素: ${x} ${tail}" )

匹配元组

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {

    val tuple =("zhangsan",18,"北京朝阳区")

    tuple match {
      case (x,y,z) => println(s"${x},${y},${z}")
    }
  }
代码语言:javascript
复制
zhangsan,18,北京朝阳区

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

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

代码语言:javascript
复制
 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)

普通的方式。

代码语言:javascript
复制
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)
    }
  }

输出结果

代码语言:javascript
复制
王昭君1
王昭君2
王昭君3
王昭君4

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

代码语言:javascript
复制
   def readStuName(tlist:List[(String, (String, (String, (String, Int))))] ): Unit ={
     for(e <- tlist){
       val value: String = e._2._2._2._1
       println(value)
     }
   }

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

模式匹配的方式

代码语言:javascript
复制
  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)
  }

结果

代码语言:javascript
复制
王昭君1
王昭君2
王昭君3
王昭君4

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

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

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

代码语言:javascript
复制
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}")
       }
     }
   }

结果

代码语言:javascript
复制
姓名:王昭君1 学校:宝安中学1
姓名:王昭君2 学校:宝安中学2
姓名:王昭君3 学校:宝安中学3
姓名:王昭君4 学校:宝安中学4

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

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

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

代码语言:javascript
复制
tlist.foreach{case (area,(school,(clazz,(stuName,id)))) => println(s"姓名:${stuName} 学校:${school}")}

匹配对象和样例类

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

语法:

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

定义一个类:

代码语言:javascript
复制
class Person(val name:String,val age:Int,val sex:Char)

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

代码语言:javascript
复制
case class Person(val name:String,val age:Int,val sex:Char)

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

代码语言:javascript
复制
Person.apply("张三",18,'男')

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

代码语言:javascript
复制
Person("张三",18,'男')

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

获取样例类数据

代码语言:javascript
复制
println(person.name) // 张三
println(person.age) // 18
println(person.sex) // 男

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

使用样例类进行模式匹配

代码语言:javascript
复制
def main(args: Array[String]): Unit = {
    val person=Person("张三",18,'男')

    person match {
      case Person(x,y,z) => println(s"姓名:$x;年龄:$y;性别:$z")
    }
}
代码语言:javascript
复制
姓名:张三;年龄:18;性别:男

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

代码语言:javascript
复制
class Student(val name:String,val age:Int,val sex:Char)

创建对象

代码语言:javascript
复制
val student=new Student("李四",20,'男')

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

代码语言:javascript
复制
Cannot resolve method student.unapply
Cannot resolve symbol student

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

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

代码语言:javascript
复制
  object  Student{
    def unapply(arg: Student): Option[(String, Int, Char)] = {
      if(arg == null) None
      else Some((arg.name,arg.age,arg.sex))
    }
  }

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

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

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

定义一个元组

代码语言:javascript
复制
val t =("张三",18)

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

代码语言:javascript
复制
val t =("张三",18)
println(t._1)
println(t._2)

其实可以换种方式

代码语言:javascript
复制
val (name,age) =("张三",18)
println(name)
println(age)

当然也可以用再List

代码语言:javascript
复制
val List(x,y,z)=List(1,2,3)
println(x,y,z)

对象也是可以的。

代码语言:javascript
复制
val Person(name,age,sex)=Person("张三",18,'男')
println(name,age,sex)

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

代码语言:javascript
复制
val person=Person("张三",18,'男')
person match {
  case Person(name,age,sex) => println(s"$name,$age,$sex")
}

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

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

定义一个map

代码语言:javascript
复制
val map=Map("name"-> "张三","age"->18,"sex"->'男')

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

代码语言:javascript
复制
for((k,v)<-map){
  println(s"$k = $v")
}
代码语言:javascript
复制
name = 张三
age = 18
sex = 男

对象亦是如此

代码语言:javascript
复制
  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 很香),但都属于不同的对象。

代码语言:javascript
复制
张三, 18,男
张三, 18,男
张三, 18,男
张三, 18,男

偏函数中的模式匹配

什么叫偏函数

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

偏函数定义

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

 偏函数格式说明
偏函数格式说明

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

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

代码语言:javascript
复制
    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))))
    )

使用模式匹配的方式获取

代码语言:javascript
复制
list.foreach({
      case (area,(school,(clazz,(stuName,id)))) => println(s"姓名:$stuName,学校:$school")
})

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

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

使用偏函数的方式

代码语言:javascript
复制
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 :表示返回值类型,因为这里只是打印所有不需返回

代码语言:javascript
复制
姓名:王昭君1,学校:宝安中学1
姓名:王昭君2,学校:宝安中学2
姓名:王昭君3,学校:宝安中学3
姓名:王昭君4,学校:宝安中学4

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

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

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

代码语言:javascript
复制
list.foreach(println(fun1))

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

代码语言:javascript
复制
// 调用
list.foreach(fun1)

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

代码语言:javascript
复制
val newList: List[String] = list.map(e => fun1(e))

// 打印
println(newList.mkString(","))
代码语言:javascript
复制
姓名:王昭君1,学校:宝安中学1,姓名:王昭君2,学校:宝安中学2,姓名:王昭君3,学校:宝安中学3,姓名:王昭君4,学校:宝安中学4

最后:

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

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 语法:
  • 案例演示
    • 案例一演示
      • 案例二演示
      • 模式守卫
      • 类型匹配
        • 匹配常量
          • 匹配类型
            • 匹配数组
              • 匹配List
                • 匹配元组
                  • 匹配对象和样例类
                    • 变量声明,for循环模式匹配
                      • 偏函数中的模式匹配
                      • 最后:
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档