文章目录
8. 模式匹配
Scala中的模式匹配类似于Java中的switch语法
但是scala从语法中补充了更多的功能,所以更加强大。
8.1 基本语法
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句。
package demo
object TestMatch {
def main(args: Array[String]): Unit = {
var a=10
var b=20
var op:Char='+'
//模式匹配是有返回值的
//模式匹配中,没有break关键字,case分支执行结束后,直接跳出
var res=op match {
case '+'=>a+b
case '-'=>a-b
case '*'=>a*b
case '/'=>a/b
case _=>"位置运算符"
}
println(res)
}
}
1)说明
(1)如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句,若此时没有case _ 分支,那么会抛出MatchError。
(2)每个case中,不需要使用break语句,自动中断case。
(3)match case语句可以匹配任何类型,而不只是字面量。
(4)=> 后面的代码块,直到下一个case语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括。
8.2 模式守卫
1)说明
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
2)案例实操
object TestMatch {
def main(args: Array[String]): Unit = {
//通过模式守卫,求一个整数的绝对值
def abs(num:Int):Int={
num match{
case i:Int if i>=0 =>i
case j:Int if j<0 => -j
}
}
println(abs(-5))
}
}
8.3 模式匹配类型
8.3.1 匹配常量
1)说明
Scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。
2)实操
object TestMatch {
def main(args: Array[String]): Unit = {
//匹配常量
def fun(x:Any):String={
x match {
case 5=>"five"
case "hello"=>"String hello"
case true=>"true"
case '+'=>"+"
}
}
println(fun("5"))
}
}
由于没有case _ 报错
8.3.2 匹配类型
1)说明
需要进行类型判断时,可以使用前文所学的isInstanceOf[T]和asInstanceOf[T],也可使用模式匹配实现同样的功能。
2)案例实操
object TestMatch {
def main(args: Array[String]): Unit = {
//匹配类型
def fun(x:Any):String={
x match {
case i:Int=>"Int"
case s:String=>"String"
case a:Array[_]=>"Array" //泛型擦除,在匹配的时候,与泛型无关
//如果以上case都没有匹配上的话,那么下面可以用下划线表示默认情况,但是通过下划线没有办法获取匹配内容
//如果想要获取匹配的值,那么可以定义一个变量去接收
case some=>some+"暂时未知"
}
}
//接收匹配的类型语法格式 定义对应类型的变量去接收
println(fun(Array(1,2,3)))
}
}
8.3.3 匹配数组
1)说明
scala模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为0的数组。
2)案例实操
object TestMatch {
def main(args: Array[String]): Unit = {
//匹配数组
for (arr<-List(
Array(0),
Array(0,1),
Array(1,2),
Array(1,2,3),
Array(1,2,3,4))){
println(arr match {
case Array(0) => "0" //匹配Array(0)这个数组
case Array(x, y) => x + "," + y //匹配有两个元素的数组,然后将其元素赋值给想x,y
case Array(0, _*) => "以0开头的数组" //匹配以0开头的数组
case _ => "other"
})
}
}
}
8.3.4 匹配列表
1)方式一
object TestMatch {
def main(args: Array[String]): Unit = {
//匹配列表
for (arr<-List(
List(0),
List(0,1),
List(1,2),
List(1,2,3),
List(1,2,3,4),
List(88))){
println(arr match {
case List(0) => "0"
case List(x, y) => x + "," + y
case List(0, _*) => "以0开头的数组"
case List(a)=>a
case _ => "other"
})
}
}
}
2)方式二
object TestMatchList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 5, 6, 7)
list match {
case first :: second :: rest => println(first + "-" + second + "-" + rest)
case _ => println("something else")
}
}
}
8.3.5 匹配元组
object TestMatchTuple {
def main(args: Array[String]): Unit = {
//对一个元组集合进行遍历
for (tuple <- Array((0, 1), (1, 0), (1, 1), (1, 0, 2))) {
val result = tuple match {
case (0, _) => "0 ..." //是第一个元素是0的元组
case (y, 0) => "" + y + "0" // 匹配后一个元素是0的对偶元组
case (a, b) => "" + a + " " + b
case _ => "something else" //默认
}
println(result)
}
}
}
练习
object TestMatch {
def main(args: Array[String]): Unit = {
//扩展样例
val list = List(("a", 1), ("b", 2), ("c", 3))
//对list遍历,输出第一个元素
for (elem <- list) {
println(elem._1)} //可读性差
//特殊的模式匹配1
for ((word,count) <- list) {println(word)}
for ((word,_) <- list) {println(word)}
for (("a",count) <- list) {println(count)}
//特殊的模式匹配2 在模式匹配的时候,给元组元素命名
val (id,name,age) = (100,"zmzdmx",18)
println(name)
//元组key,不变,value*2
val newlist = list.map(t=>(t._1,t._2*2))
val newlist1 = list.map(t=>{
t match {
case (word,count)=>(word,count*2)
}
})
//如果匿名函数中,使用模式匹配case,要求必须用花括号括起来
//如果一个函数中,只有一个参数,那么参数列表的小括号可以花括号代替
val newlist2 = list.map{ case (word,count)=>(word,count*2)}
//练习:使用模式匹配 对count*2
val list1 = List(("a",("a",5)),("b",("b",5)),("c",("c",5)))
val newlist3=list1.map{
case (key,(word,count))=>(key,(word,count*2))
}
}
}
8.3.6 匹配对象及样例类
1)基本语法
class User(val name: String, val age: Int)
object User{
def apply(name: String, age: Int): User = new User(name, age)
def unapply(user: User): Option[(String, Int)] = {
if (user == null)
None
else
Some(user.name, user.age)
}
}
object TestMatchUnapply {
def main(args: Array[String]): Unit = {
val user: User = User("zhangsan", 11)
val result = user match {
case User("zhangsan", 11) => "yes"
case _ => "no"
}
println(result)
}
}
小结
val user = User(“zhangsan”,11),该语句在执行时,实际调用的是User伴生对象中的apply方法,因此不用new关键字就能构造出相应的对象。
当将User(“zhangsan”, 11)写在case后时[case User(“zhangsan”, 11) => “yes”],会默认调用unapply方法(对象提取器),user作为unapply方法的参数,unapply方法将user对象的name和age属性提取出来,与User(“zhangsan”, 11)中的属性值进行匹配
case中对象的unapply方法(提取器)返回Some,且所有属性均一致,才算匹配成功,属性不一致,或返回None,则匹配失败。
若只提取对象的一个属性,则提取器为unapply(obj:Obj):Option[T]
若提取对象的多个属性,则提取器为unapply(obj:Obj):Option[(T1,T2,T3…)]
若提取对象的可变个属性,则提取器为unapplySeq(obj:Obj):Option[Seq[T]]
2)样例类
(1)语法:
case class Person (name: String, age: Int)
(2)说明
样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如apply、unapply、toString、equals、hashCode和copy。
样例类是为模式匹配而优化的类,因为其默认提供了unapply方法,因此,样例类可以直接使用模式匹配,而无需自己实现unapply方法。
构造器中的每一个参数都成为val,除非它被显式地声明为var(不建议这样做)
(3)实操
上述匹配对象的案例使用样例类会节省大量代码
case class User(name: String, age: Int)
object TestMatchUnapply {
def main(args: Array[String]): Unit = {
val user: User = User("zhangsan", 11)
val result = user match {
case User("zhangsan", 11) => "yes"
case _ => "no"
}
println(result)
}
}
8.4 变量声明中的模式匹配
case class Person(name: String, age: Int)
object TestMatchVariable {
def main(args: Array[String]): Unit = {
val (x, y) = (1, 2)
println(s"x=$x,y=$y")
val Array(first, second, _*) = Array(1, 7, 2, 9)
println(s"first=$first,second=$second")
val Person(name, age) = Person1("zhangsan", 16)
println(s"name=$name,age=$age")
}
}
8.5 for表达式中的模式匹配
object TestMatchFor {
def main(args: Array[String]): Unit = {
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for ((k, v) <- map) { //直接将map中的k-v遍历出来
println(k + " -> " + v) //3个
}
println("----------------------")
//遍历value=0的 k-v ,如果v不是0,过滤
for ((k, 0) <- map) {
println(k + " --> " + 0) // B->0
}
println("----------------------")
//if v == 0 是一个过滤的条件
for ((k, v) <- map if v >= 1) {
println(k + " ---> " + v) // A->1 和 c->33
}
}
}
8.6 偏函数中的模式匹配
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为List[Int],而我们需要的是第一个元素是0的集合,这就是通过模式匹配实现的。
1)偏函数定义
val second: PartialFunction[List[Int], Option[Int]] = {
case x :: y :: _ => Some(y)
}
注:该偏函数的功能是返回输入的List集合的第二个元素
2)偏函数原理
上述代码会被scala编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数检查的函数——isDefinedAt,其返回值类型为Boolean。
val second = new PartialFunction[List[Int], Option[Int]] {
//检查输入参数是否合格
override def isDefinedAt(list: List[Int]): Boolean = list match {
case x :: y :: _ => true
case _ => false
}
//执行函数逻辑
override def apply(list: List[Int]): Option[Int] = list match {
case x :: y :: _ => Some(y)
}
}
3)偏函数使用
偏函数不能像second(List(1,2,3))这样直接使用,因为这样会直接调用apply方法,而应该调用applyOrElse方法,如下
second.applyOrElse(List(1,2,3), (_: List[Int]) => None)
applyOrElse方法的逻辑为 if (ifDefinedAt(list)) apply(list) else default。如果输入参数满足条件,即isDefinedAt返回true,则执行apply方法,否则执行defalut方法,default方法为参数不满足要求的处理逻辑。
4)案例实操
(1)需求
将该List(1,2,3,4,5,6,“test”)中的Int类型的元素加一,并去掉字符串。
def main(args: Array[String]): Unit = {
val list = List(1,2,3,4,5,6,"test")
val list1 = list.map {
a =>
a match {
case i: Int => i + 1
case s: String =>s + 1
}
}
println(list1.filter(a=>a.isInstanceOf[Int]))
}
(2)实操
方法一:
List(1,2,3,4,5,6,"test").filter(.isInstanceOf[Int]).map(.asInstanceOf[Int] + 1).foreach(println)
方法二:
List(1, 2, 3, 4, 5, 6, "test").collect { case x: Int => x + 1 }.foreach(println)