前言
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 (布尔表达式) => ...
...
}
案例:校验用户密码;
- 首先要满足 8位及以上长度
- 不能为纯数字
- 不能为纯字母
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
最后:
关于模式匹配的知识到这里也就完了,有什么疑问或者我没有补充到的,欢迎下方探讨。