【scala系列】9、Scala模式匹配


1. 模式匹配介绍

模式匹配是检查某个值(value)是否匹配某一个模式的机制,一个成功的匹配同时会将匹配值解构为其组成部分。它是Java中的switch语句的升级版,同样可以用于替代一系列的 if/else 语句。

1.1 语法

一个模式匹配语句包括一个待匹配的值,match关键字,以及至少一个case语句。

match 对应 Java 里的 switch,但是写在选择器表达式之后。即: 选择器 match {备选项}。

match 表达式通过以代码编写的先后次序尝试每个模式来完成计算,只要发现有一个匹配的case,剩下的case不会继续匹配。


import scala.util.Random

val x: Int = Random.nextInt(10)

x match {
  case 0 => "zero"
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}

// 上述代码中的val x是一个0到10之间的随机整数,
// 将它放在match运算符的左侧对其进行模式匹配,
// match的右侧是包含4条case的表达式,
// 其中最后一个case _表示匹配其余所有情况,在这里就是其他可能的整型值。

2. 模式匹配种类

2.1 通配符匹配

即通配符匹配 _ ,匹配所有的情况


  /**
    * 通配符匹配
    * @param x
    * @return
    */
  def m1(x: Any) = x match {
    case List(0, _, _) => "匹配 0 元素开头的list"
    case List(1, _*) => "匹配 1 元素开头的list"
    case Vector(1, _*) => "匹配 1 元素开头的vector"
    case m: Map[_, _] => m.toString
    case _ => "Unknown"
  }

  println(m1(List(0,1,2))) //匹配 0 元素开头的list
  println(m1(List(1,2))) //匹配 1 元素开头的list
  println(m1(Vector(1,2,3))) //匹配 1 元素开头的vector

2.2 常量匹配


def m2(x:Any) = x match {
  case 0 => println("zero")
  case true => println("true")
  case "hello" => println("you said 'hello'")
  case Nil => println("an empty List")
  case _ => println("unknow")
}

println(m2("hello")) //you said 'hello'
println(m2(true)) //true

2.3 变量匹配

  • 常量匹配很简单,即case后跟的都是常量
  • 变量匹配需要注意,case后跟的是match里面的临时变量,而不是其他变量名:
def m3(str:String) = str match {
    case "a" => 1
    case "b" =>"b"
    case _   => -1
}

println(m1("a")) //1
println(m1("b")) //b


3 match {
  case i => println("i=" + i) // 这里i是模式变量(临时变量),就是3
}

val a = 20

20 match { case a => 1 } // 1, a是模式变量,不是10
//为了使用变量a,必须用`a`:
20 match { case `a` => 1; case b => -1 } // -1,`a`是变量10
//或者用大写的变量
val A = 10
20 match { case A => 1; case b => -1 } // -1,大写A是变量10

2.4 构造函数匹配

构造器模式不只检查顶层对象是否一致,还会检查对象的内容是否匹配内层的模式。由于额外的模式自身可以形成构造器模式,因此可以使用它们检查到对象内部的任意深度。


object ConstructorPattern {

  def main(args: Array[String]): Unit = {

    val a1 = Cat("多啦A梦", "白色")
    val s1 = what(a1)
    println(s1) //a cat name is 多啦A梦,color is 白色

    val a2 = Dog("多啦B梦", "白色", 500)
    val s2 = what(a2)
    println(s2) //a dog name is 多啦B梦,color is 白色 ,age is 500
  }

  def what(animal: Animal): String = animal match {
    case Cat(name: String, color: String) => s"cat's is $name,color $color"
    case Dog(name: String, color: String, age: Int) => s"dog's is $name,color $color ,age $age"
  }
}

2.5 集合类型匹配

/**
  * 匹配数组
*/
def m5(arr: Array[Int]) = arr match {
    case Array(1, x, y) => println("匹配以1 开头,有三个元素的数组");
    case Array(0) => println("匹配只有 0 这个元素的数组");
    case Array(0, _*) => println("匹配以0 开头任意多个元素的数组");
    case arr if arr.length == 2 => println("length = 2")
    case _ => println("unknow")
}

println(m5(Array(1, 2, 3))) //匹配以1 开头,有三个元素的数组
println(m5(Array(0, 2, 3))) //匹配以0 开头任意多个元素的数组
println(m5(Array(0))) //匹配只有 0 这个元素的数组
println(m5(Array(9, 0))) //length = 2


/**
    * 匹配序列list
*/
def m5_1(list: List[Int]) = list match {
    case 5 :: Nil => println("匹配只有 5 这个元素的序列");
    case x :: y :: Nil => println("匹配只有两个元素的序列");
    case x :: tail => println("匹配任意多个元素的数组");
    case list if list.last == 1 => println()
    case _ => println("unknow")
}

println(m5_1(List(5,6,7))) //匹配任意多个元素的数组
println(m5_1(List(0,4))) //匹配只有两个元素的序列

2.6 元组类型匹配

/**
 * 匹配元组list
*/
def m6(tuple: Any) = tuple match {
    case (x, y, 7) => println("匹配有三个元素并且以7 结尾的元组");
    case (2, x, y) => println("匹配以2 开头有三个元素的元组");
    case _ => println("unknow")
}

println(m6((1,2,7))) //匹配有三个元素并且以7 结尾的元组
println(m6((2,2,0))) //匹配以2 开头有三个元素的元组

2.7 类型匹配

match 可以很简单地匹配数据类型(不需要isInstanceOf[T]):
为了让匹配更加具体,可以使用模式守卫,也就是在模式后面加上if

/**
 * 匹配类型
*/
def m7(x: Any): String = x match {
    case x: String => x
    case x: Int if x > 5 => x.toString //带if守卫条件的匹配
    case _ => "unknow"
}

println(m7("hello")) //hello
println(m7(9)) // 9 
println(m7(2)) // unknow ,(虽然2满足Int类型, 但是不满足守卫条件"大于5",所以往下匹配)


def m7_1(v: Any) = v match {
    case null => "null"
    case i: Int => i * 100
    case s: String => s
    case _ => "others"
}
// 注意:上面case中的i、s都叫模式变量
println(m7_1(null)) // "null"
println(m7_1(5)) // 500
println(m7_1("hello")) // "hello"
println(m7_1(3.14)) // "others"

注意:自定义类型如果也要匹配,需要用case class

3. 模式匹配基本应用

3.1 match..case 和 switch..case的区别

// Java写法
switch(n) {
      case(1): ...; break;
      case(2): ...; break;
      default: ...;
}

// Scala写法 ,每次结束不用写break , _相当于default
def m(n:String) = n match {
  case "a" | "b" => ...
  case "c" => ...
  case _ => ...
}

3.2 命令行参数解析例子

/** Basic command line parsing. */
object Main {
  var verbose = false // 记录标识,以便能同时对-h和-v做出响应
   def main(args: Array[String]) {
    for (a <- args) a match {
      case "-h" | "-help" => println("Usage: scala Main [-help|-verbose]") 
      case "-v" | "-verbose" => verbose = true 
      case x => println("Unknown option: '" + x + "'") // 这里x是临时变量
    }
    if (verbose) println("How are you today?")
  }
}

3.3 使用case的递归函数


def fac1(n:Int):Int = n match {
    case 0 => 1 
    case _ => n * fac1( n - 1 )
}
fac1(5) //120

// 同
def fac2: Int => Int = {
    case 0 => 1
    case n => n * fac2( n - 1 )
}
fac2(5) //120

// 同 使用尾递归
def fac3: (Int,Int) => Int = {
    case (0,y) => y
    case (x,y) => fac3(x-1, x*y)
}
fac3(5,1) // 120

// 同 reduceLeft
def fac4(n:Int) = 1 to n reduceLeft( _ * _ )

implicit def foo(n:Int) = new { def ! = fac4(n) }
5! // 120

// 同
def fac5(n:Int) = (1:BigInt) to n product
fac5(5) // 120

3.4 case..if条件匹配


(1 to 20) foreach {
  case x if (x % 15 == 0) => printf("%2d:15n\n",x)
  case x if (x % 3 == 0) => printf("%2d:3n\n",x)
  case x if (x % 5 == 0) => printf("%2d:5n\n",x)
  case x => printf("%2d\n",x)
}

// 或者
(1 to 20) map (x=> (x%3,x%5) match {
  case (0,0) => printf("%2d:15n\n",x) 
  case (0,_) => printf("%2d:3n\n",x)
  case (_,0) => printf("%2d:5n\n",x) 
  case (_,_) => printf("%2d\n",x)
})

3.5 try..catch..finally

var f = openFile()
try {
  f = new FileReader("inputPath")
} catch {
  case ex: FileNotFoundException => // Handle missing file
  case ex: IOException => // Handle other I/O error
} finally {
    f.close()
}

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
 上一篇
【scala系列】10、class(类)、object(对象)、trait(特质) 【scala系列】10、class(类)、object(对象)、trait(特质)
1. 对象(object)在scala 中被object 关键字修饰的类有一下特征 没有 有参数的主构造器, 但是有主构造器代码块(不包含在任何方法中的代码,就是主构造器代码) 它是单例的所以主构造器代码块只会执行一次 不需要通过关键字
2020-04-12
下一篇 
【scala系列】8、方法与函数 【scala系列】8、方法与函数
1. 方法 Scala 中使用 def 语句定义方法, val 语句定义函数。 定义方法的通用格式 :def functionName ([参数列表]) : [return type] = { 方法体 } 。 如果一个方法有返回值,方法
2020-04-05
  目录