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

前言

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

最后:

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容