Scala 高阶(九):Scala中的模式匹配

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情


本次主要分享Scala中关于模式匹配的内容,Scala中的模式匹配类似于Java中的switch语法,但是Scala在基于Java的思想上补充了特有的功能。

一、概述

基本语法

value match {
    case caseVal1 => returnVal1
    case caseVal2 => returnVal2
    ...
    case _ => defaultVal
}

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

举个例子:

  val x: Int = 2
    val y: String = x match {
      case 1 => "one"
      case 2 => "two"
      case 3 => "three"
      case _ => "other"
    }

第二个例子:

    // 示例:使用模式匹配实现简单的二元运算
    val a = 25
    val b = 21

    def matchOualop(op: Char) = op match {
      case '+' => a + b
      case '-' => a - b
      case '*' => a * b
      case '/' => a / b
      case _ => "非法运算"
    }

    println(matchOualop('+'))
    println(matchOualop('-'))
    println(matchOualop('\\'))
  • 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句,若此时没有 case _ 分支,那么会抛出 MatchError
  • 每个 case 中,不需要使用 break 语句,自动中断 case
  • match case 语句可以匹配任何类型,而不只是字面量。
  • => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括。

二、模式守卫

需要进行匹配某个范围的数据内容的时候,可以在模式匹配中进行模式守卫的操作,类似于for推倒式中的循环守卫。

举个例子:

object Test01_PatternMatchBase {
  def main(args: Array[String]): Unit = {
    // 模式守卫
    // 求一个整数的绝对值
    def abs(num: Int) = num match {
      case i if i >= 0 => i
      case i if i < 0 => -i
    }

    println(abs(11))
    println(abs(-11))
  }
}

三、模式匹配类型

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

常量

    // 匹配常量
    def describeConst(s: Any): String = s match {
      case 1 => "number 1"
      case "hello" => "String hello"
      case '+' => "+"
      case 's' => "Char s"
      case _ => ""
    }
    
    println(describeConst(1))
    println(describeConst('s'))
    println(describeConst(11))

类型

	// 匹配类型
	 def deprecateType(x: Any): String = x match {
	      case i: Int => "int" + i
	      case s: String => "String" + s
	      case list: List[String] => "list" + list
	      case array: Array[Int] => "Array[Int]" + array.mkString("-")
	      case a => "Something type"
	    }

    println(deprecateType(23))
    println(deprecateType("12"))
    println(deprecateType(List("mi", "hao")))
    println(deprecateType(List(12, 21)))

数组

对于数组可以定义多种匹配形式,可以定义模糊的元素类型匹配、元素数量匹配或者精确的某个数组元素值匹配

	// 匹配数组
    for (arr <- List(
      Array(0),
      Array(1, 0),
      Array(0, 1, 0),
      Array(1, 1, 0),
      Array(12, 21, 34, 32),
      Array("hello", 34, 32),
    )) {
      val result = arr match {
        case Array(0) => "0"
        case Array(1, 0) => "Array(1,0)"
        case Array(x, y) => "Array:" + x + ", " + y
        case Array(0, _*) => "以0开头的数组"
        case Array(x, 1, y) => "中间为1 的三元数组"
        case _ => "something else"
      }
    }

列表

使用::运算符匹配first :: second :: rest,将一个列表拆成三份,第一个第二个元素和剩余元素构成的列表。

    // 匹配列表 方式一
    for (list <- List(
      List(0),
      List(1, 0),
      List(0, 0, 0),
      List(1, 1, 0),
      List(999)
    )) {
      val result = list match {
        case List(0) => "0"
        case List(x, y) => "list:" + x + ", " + y
        case List(0, _*) => "以0开头的list"
        case List(a) => "list(a)" + a
        case _ => "something else"
      }
    }

    // 方式二
    val list = List(1, 34, 3, 2, 3, 2, 3, 6)
    list match {
      case first :: second :: rest => println(s"first:$first second: $second rest: $rest")
      case _ => "something else"
    }

元组

可以匹配n元组、匹配元素类型、匹配元素值。如果只关心某个元素,其他就可以用通配符或变量。元组大小固定,所以不能用_*

    // 元组类型
    for (tuple <- List(
      (0, 1),
      (0, 1),
      (0, 1, 0),
      (0, 1, 1),
      ("hello", true, 0.5)
    )) {
      val result = tuple match {
        case (a,b) => " "+ a + ", " +b
        case (0,_) => "(0,_)"
        case (a,1,_) => "(a,1,_)" + a
        case (x,y,z) => "(x,y,z)"
        case _ => "something else"
      }
    }

对象及样例类

匹配对象

object Test04_MathObject {
  def main(args: Array[String]): Unit = {
    val student = new Student("alice", 15)

    //针对对象的实例进行匹配
    val result = student match {
      case Student("alice", 15) => "alice, 15"
      case _ => "else"
    }
  }
}

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

object Student {
  def apply(name: String, age: Int): Student = new Student(name, age)

  // 实现unapply
  def unapply(student: Student): Option[(String, Int)] = {
    if (student == null){
      None
    } else{
      Some(student.name,student.age)
    }
  }
}
  • 当将 Student("alice", 15))写在 case 后时 case Student("alice", 15) => "alice, 20",会默认调用 unapply 方法(对象提取器),student 作为 unapply 方法的参数,unapply 方法将 student 对象的 nameage 属性提取出来,与 Student("alice", 15)) 中的属性值进行匹配
  • case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功,

属性不一致,或返回 None,则匹配失败。

  • 若只提取对象的一个属性,则提取器为 unapply(obj:Obj):Option[T]
  • 若提取对象的多个属性,则提取器为 unapply(obj:Obj):Option[(T1,T2,T3…)]
  • 若提取对象的可变个属性,则提取器为 unapplySeq(obj:Obj):Option[Seq[T]]

样例类

case class 类名 (参数1,参数2,......)
  • 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如 apply、unapply、toString、equals、hashCodecopy
  • 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。
  • 构造器中的每一个参数都成为 val修饰的变量
object Test_MatchCaseClass {
  def main(args: Array[String]): Unit = {
    val student = Student("alice", 15)

    val result = student1 match {
      case Student("alice", 15) => "alice, 20"
      case _ => "else"
    }
    println(result)
  }
}

case class Student(name: String, age: Int)

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

变量声明也可以是一个模式匹配的过程。

object Test_MathTupleExtend {
  def main(args: Array[String]): Unit = {
    // 1.在变量声明时匹配
    val (x,y) = (10,"hello")
    println(s"$x $y")
    val List(first,second,_*) = List(12,21,21,21,34)
    println(s"$first $second ")

    val fir :: sec :: res =List(12,21,34)
    println(s"$fir $sec $res")
  }
}

五、for表达式模式匹配

  • 元组中取元素时,必须用_1 _2 ...,可以用元组赋值将元素赋给变量
  • 指定特定元素的值,可以实现类似于循环守卫的功能
object TestMatchFor {
	 def main(args: Array[String]): Unit = {
		 val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
		 // //直接将 map 中的 k-v 遍历出来
		 for ((k, v) <- map) { 
		 	println(k + " -> " + v) //3 个
		 }

		 //遍历 value=0 的 k-v ,如果 v 不是 0,过滤
		 for ((k, 0) <- map) {
			 println(k + " --> " + 0) // B->0
		 }

		 //if v == 0 是一个过滤的条件
		 for ((k, v) <- map if v >= 1) {
			 println(k + " ---> " + v) // A->1 和 c->33
		 }
	 } 
 }

六、偏函数模式匹配

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

偏函数定义

val second: PartialFunction[List[Int], Option[Int]] = {
 case x :: y :: _ => Some(y)
}
  • second:偏函数名称
  • PartialFunction[List[Int], Option[Int]]: 偏函数类型
  • 该偏函数的功能是返回输入的 List 集合的第二个元素

举个例子:

object Test_PartialFunction {
  def main(args: Array[String]): Unit = {
    val list = List(("a,", 12), ("b", 34), ("c", 45))

    // map转换 key不变 value两倍
    val newList = list.map(tuple => (tuple._1, tuple._2 * 2))

    // 模式匹配 对元素元素赋值
    val newList2 = list.map(
      tuple => {
        tuple match {
          case (word, count) => (word, count * 2)
        }
      }
    )

    // 省略lambda表达式 表示偏函数
    val newList3 = list.map {
      case (word, count) => (word, count * 2)
    }

    // 函数应用 求绝对值
    val positiveAbs: PartialFunction[Int, Int] = {
      case x if x >= 0 => x
    }

    val pnegativeAbs: PartialFunction[Int, Int] = {
      case x if x < 0 => -x
    }

    def abs(x: Int): Int= (positiveAbs orElse pnegativeAbs)(x)

    println(abs(21))
  }
}

本次Scala中的模式匹配部分到这里就结束了,知识点较为简单但是使用起来特别的灵活,希望对大家有所帮助!!!

猜你喜欢

转载自juejin.im/post/7126744884437843998