[Scala]Scala学习笔记十 模式匹配

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/77301598

Scala有一个十分强大的模式匹配机制.可以应用在很多场合:switch语句,类型检查等等.此外Scala还提供了样例类,对模式匹配进行了优化.

1. 更好的switch

如下是Scala中C风格switch语句的等效代码:

var sign = 2
val ch = '+'
ch match {
  case '+' => sign = 1
  case '-' => sign = -1
  case _ => sign = 0
}
println(sign) // 1

与default等效的是捕获所有模式(case _模式).有这样一个捕获所有的模式是有好处的.如果没有模式能匹配并且并没有提供case _,代码会抛出MatchError.

与C语言的switch不同的是,Scala模式匹配不会像C语言那样必须在每个分支的末尾显示使用break语句防止进行下一分支.

与if类似,match也是表达式,而不是语句.前面代码可以简化为:

sign = ch match {
  case '+' => 1
  case '-' => -1
  case _ => 0
}

可以在match表达式中使用任何类型,而不仅仅是数字:

color match{
  case Color.RED => ...
  case Color.BLACK => ...
  ...
}

2. 守卫

假如说我们想扩展上面第一个代码以匹配所有数字.在C风格switch语句中可以通过简单的添加多个case标签来实现:

switch(i){
  case 0:
    ...
    break;
  case 1:
    ...
    break;
  ...
  case 9:
    ...
    break;  
  default:
    ...
}

这如果放在Scala中会更简单,给模式添加守卫(相当于一个判断条件)即可:

ch match{
  case '+' => sign = 1
  case '-' => sign = -1
  case _ if Character.isDigit(ch) => digit = Character.digit(ch, 10)
  case _ => sign = 0
}

守卫可以是任何Boolean条件.

备注

模式总是从上往下进行匹配的.如果守卫的这个模式不能匹配,则捕获所有的模式(`case _`)会被用来尝试进行匹配.

3. 模式中的变量

如果case关键字后面跟着一个变量名,那么匹配的表达式会被赋值给那个变量,进而可以在后面中使用该变量:

val character = '1'
character match {
  case '+' => println("this is +")
  case '-' => println("this is -")
  case ch => println("this is " + ch) // this is 1
}

备注

上面代码中如果给定字符不是'+'或者'-',给定字符则会赋值给变量ch

也可以在守卫中使用变量:

val character = '5'
character match {
  case '+' => println("this is +")
  case '-' => println("this is -")
  case ch if Character.isDigit(ch) => println("this is digit") // this is digit
}

4. 类型模式

可以根据表达式的类型进行匹配:

val str:Any = "Hello World"
str match {
  case s: String => println("this is string " + s) // this is string Hello World
  case x: Int => println("this is integer " + x)
  case ch => println("this is other " + ch)
}

在Scala中,我们更倾向于使用这种的模式匹配,而不是使用isInstanceOf操作符.

备注

注意模式中的变量名.当你在匹配类型的时候,必须给出一个变量名(例如上例中的s,x).否则,将会拿对象本身来进行匹配:
case _: BigInt => Int.MaxValue // 匹配任何类型为BigInt的对象
case BigInt => -1 // 匹配类型为Class的BigInt对象

5. 匹配数组,列表和元组

匹配数组中的内容,可以在模式中使用Array表达式:

def arrayMatch(arr:Array[String]) = arr match {
  case Array("Hello") => println("the array only contain 'Hello'")
  case Array(x,y) => println("the array contain two value " + x + " and " + y)
  case Array(x,_*) => println("the array contain many values " + arr.mkString(","))
  case _ => println("the other array")
}

arrayMatch(Array("Hello")) // the array only contain 'Hello'
arrayMatch(Array("Hello", "World")) // the array contain two value Hello and World
arrayMatch(Array("Hello", "World", "Yoona")) // the array contain many values Hello,World,Yoona

同样也可以使用List表达式(或者使用::操作符)匹配列表:

def listMatch(list:List[String]) = list match {
  case "Hello" :: Nil => println("the list only contain 'Hello'")
  case x :: y :: Nil => println("the list contain two value " + x + " and " + y)
  case "Hello" :: tail => println("the list contain many values " + list)
  case _ => println("the other list")
}

listMatch(List("Hello")) // the list only contain 'Hello'
listMatch(List("Hello", "World")) // the list contain two value Hello and World
listMatch(List("Hello", "World", "Yoona")) // the list contain many values List(Hello, World, Yoona)

同样也可以使用元组表示法匹配元组:

def pairMatch(t:Any) = t match {
  case ("Hello", _) => println("the first value is 'Hello'")
  case (x, "Hello") => println("the first value is " + x + " and the second value is 'Hello'")
  case _ => println("the other tuple")
}

pairMatch(("Hello", "World")) // the first value is 'Hello'
pairMatch(("World", "Hello")) // the first value is World and the second value is 'Hello'
pairMatch(("Hello", "World", "Yoona")) // the other tuple

6. 提取器

前面我们看到模式是如何匹配数组,列表和元组的,这些功能背后是提取器机制,带有从对象中提取值的unapply或unapplySeq方法的对象.unapply方法用于提取固定数量的对象,而unapplySeq提取的是一个序列.

arr match {
  case Array(0, x) => ...
  ...
}

Array伴生对象就是一个提取器,定义了一个unapplySeq方法.该方法被调用时,以被执行匹配动作的表达式作为参数.Array.unapplySeq(arr)产出一个序列的值,即数组中的值.第一个值与零进行比较,二第二个值被赋值给x.

正则表达式是另一个适合使用提取器的场景,如果正则表达式中有分组,可以使用提取器来匹配每个分组:

val pattern = "([0-9]+ ([a-z]+))".r
val str = "99 bottles"
str match {
  // num = 99 item = bottles
  case pattern(num, item) => ...
  ...
}

pattern.unapplySeq("99 bottles")生成一系列匹配分组的字符串,这些字符串分别赋值给变量num和item.

7. 变量声明中的模式

之前我们可以看到模式汇总是可以带变量的,同样我们也可以在变量声明中使用这样的模式:

// x = 1 y = 2
val (x, y) = (1, 2)

这对于使用那些返回对偶的函数是非常有用的:

val (q, r) = BigInt(10) /% 3
println("q value is " + q + " and r value is " + r)
// q value is 3 and r value is 1

备注

/%方法返回包含商和余数的对偶,而这两个值分别被变量q和r捕获到.

8. for表达式中的模式

可以在for推导表达式中使用带变量的模式.对于每一个遍历到的值,使用模式进行变量绑定:

import scala.collection.JavaConversions.propertiesAsScalaMap
for( (key, value) <- System.getProperties() ){
  println(key + " = " + value)
}

在for表达式中,失败的匹配将被跳过(不会抛出异常),下面循环打印出所有值为空白的键,跳过所有其他键:

import scala.collection.JavaConversions.propertiesAsScalaMap
for( (key, "") <- System.getProperties() ){
  println(key + " = ")
}

也可以使用守卫(注意if关键字出现在<-之后):

import scala.collection.JavaConversions.propertiesAsScalaMap
for( (key, value) <- System.getProperties() if value == ""){
  println(key + " = " + value)
}
上一篇:让Windows下的驱动乖乖在Linux中安家!


下一篇:浅谈管理系统操作日志设计(附操作日志类)