Scala模式匹配

本文深入讲解Scala中的模式匹配,涵盖基本语法、模式守卫、多种数据类型匹配、变量声明及for表达式中的模式匹配,同时介绍了偏函数的定义、原理与使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Scala中的模式匹配类似于Java中的switch语法,但是scala从语法中补充了更多的功能,其功能更加强大。

int i = 10
switch (i) {
    case 10 :
		System.out.println("10");
		break;
    case 20 : 
		System.out.println("20");
		break;
    default : 
		System.out.println("other number");
		break;
}

一.基本语法 match case

1.语法说明

1)采用match关键字声明,每个分支采用case关键字进行声明;

  • 每个case中,不需要使用break语句,自动中断case

2)当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断;

3)如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句;

  • 若此时没有case _ 分支,那么会抛出MatchError

4)match case语句可以匹配任何类型,而不只是字面量。

5)=> 后面的代码块,直到下一个case语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括。

6)模式匹配是有返回值

2.小案例:

var a: Int = 10
var b: Int = 20
var operator: Char = 'd'

var result = operator match {
	case '+' => a + b
	case '-' => a - b
	case '*' => a * b
	case '/' => a / b
	case _ => "illegal"
}

println(result)  // illegal

二.模式守卫

模式守卫:如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。

案例:模拟绝对值函数

def abs(x : Int) = x match {
	case i:Int if i>=0 => i
	case j:Int if j<0 => -j
	case _ => "type illegal"
}

println(abs(-5))

三.模式匹配类型

1.匹配常量

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

def describe(x: Any) = x match {
    case 5 => "Int five"
    case "hello" => "String hello"
    case true => "Boolean true"
    case '+' => "Char +"
}

2.匹配类型

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

def main(args: Array[String]): Unit = {
  def describe(x: Any) = x match {
    case i: Int => "Int"
    case s: String => "String hello"
    //泛型擦除:在匹配的时候,和泛型无关
    //等价于 List[_]
    case m: List[String] => "List"
    case c: Array[Int] => "Array[Int]"
    //定义变量(名自定),可以获取x的内容并做输出
    case someThing => "something else " + someThing
    // 如果以上case都没有匹配上的话,那么下面可以用下划线表示默认情况,但是通过下划线没有办法获取匹配的内容
    // case _ =>  "something else " + _  // 这是个函数,返回:<function1>
  }

  //接收匹配的类型语法格式   定义对应类型的变量去接收
  println(describe(20))

  //泛型擦除
  println(describe(List(1,2,3)))  //List
  println(describe(List("a","b","c")))  //List

  //数组是没有泛型擦除,数组在匹配的时候,会保留泛型
  println(describe(Array(1,2,3)))  //Array[Int]
  println(describe(Array("a","b","c")))  //something else [Ljava.lang.String;@3b6eb2ec
}

注意:

  • case _ 的使用:如果以上case都没有匹配上的话,那么下面可以用下划线表示默认情况,但是通过下划线没有办法获取匹配的内容
  • 泛型擦除:在case匹配的时候,和泛型无关
  • 数组是没有泛型擦除,数组在匹配的时候,会保留泛型

3.匹配数组

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

注意区分匹配数组类型arr:Array[Int]与匹配数组case Array()的区别

//匹配数组
for (arr <- List(
              Array(0),
              Array(1, 0),
              Array(0, 1, 0),
              Array(1, 1, 0),
              Array(1, 1, 0, 1),
              Array("hello", 90))) { // 对一个数组集合进行遍历

  val result = arr match {
    case Array(0) => "0" //匹配Array(0) 这个数组

    case Array(x, y) => x + "," + y //匹配有两个元素的数组,然后将将元素值赋给对应的x,y

    case Array(0, _*) => "以0开头的数组" //匹配以0开头和数组

    case _ => "something else"
  }

  println("result = " + result)
}

4.匹配列表

方式1:与匹配数组方式一致

for (list <- List(
             List(0),
             List(1, 0),
             List(0, 0, 0),
             List(1, 0, 0),
             List(88))
      ) 
  { val result = list match {
    case List(0) => "0" //匹配List(0)
    case List(x, y) => x + "," + y //匹配有两个元素的List
    case List(0, _*) => "0 ..."
    case List(_) => _
    case _ => "something else"
  }
 println(result)
}

方式2:使用双冒号

val list: List[Int] = List(1, 2, 5, 6, 7)

list match {
	//将列表中第一、第二个元素匹配至first、second ;剩余为List匹配rest
	case first :: second :: rest => println(first + "-" + second + "-" + rest)
	// 输出结果:1-2-List(5,6,7)
	case _ => println("something else")
}

5.匹配元组

for (tuple <- List(
				(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)
}

6.匹配对象

1)基本语法

class User(val name: String, val age: Int)

object User{
	//快捷键: Ctrl + j
	//根据属性创建对象
    def apply(name: String, age: Int): User = new User(name, age)
    //根据对象获取对下属你的属性
    def unapply(user: User): Option[(String, Int)] = {
        if (user == null)
            None  //省略return 
        else
            Some(user.name, user.age) //省略return 
    }
}

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)  //yes
    }
}

小结:

  • 1)val user = User("zhangsan",11)
    • 该语句在执行时,实际调用的是User伴生对象中的apply方法,因此不用new关键字就能构造出相应的对象。
  • 2)case User("zhangsan", 11) => "yes"
    • User("zhangsan", 11)写在case后时,会默认调用unapply方法(对象提取器)
    • user作为unapply方法的参数,unapply方法将user对象的name和age属性提取出来,与User(“zhangsan”, 11)中的属性值进行匹配
  • 3)case中对象的unapply方法(提取器)返回Some,且所有属性均一致,才算匹配成功;属性不一致,或返回None,则匹配失败。
  • 4)若只提取对象的一个属性,则提取器为unapply(obj:Obj):Option[T]
  • 5)若提取对象的多个属性,则提取器为unapply(obj:Obj):Option[(T1,T2,T3…)]
  • 6)若提取对象的可变个属性,则提取器为unapplySeq(obj:Obj):Option[Seq[T]]

7.样例类

6中的匹配对象,可以直接通过声明样例类的方式实现,而不用去编写伴生对象及其apply和unapply方法,具体方式如下:

//样例类
case class Student(var name:String,var age:Int){}

object TestMatch {
  def main(args: Array[String]): Unit = {
    var std1:Student = Student("zhangsan",18)
    var res = std1 match {
      case Student("zhangsan",18) =>"success"
      case _ =>"fail"
    }
    println(res)
  }
}

说明:

  • 1)样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如apply、unapply、toString、equals、hashCode和copy。
  • 2)样例类是为模式匹配而优化的类,因为其默认提供了unapply方法,因此,样例类可以直接使用模式匹配,而无需自己实现unapply方法。
  • 3)构造器中的每一个参数都成为val,除非它被显式地声明为var(不建议这样做)

在这里插入图片描述

四.变量声明中的模式匹配

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) = Person("zhangsan", 16)
        println(s"name=$name,age=$age")
    }
}

五.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
        }
    }
}

六.偏函数中的模式匹配

偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为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方法为参数不满足要求的处理逻辑。

具体案例:

  • 将该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]))
}

简化操作:

方法一:
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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值