模式匹配

在前面的章节已经讲了Scala中的简单模式匹配。模式匹配是一个表达式,因此它会导致一个值,该值可能被分配或返回。例如:

44 match {
    case 44 => true     // 如果匹配了44,则结果为true
    case _ => false     // 否则,结果是false
}

也可以匹配字符串,例如:

"过期商品" match {
    case "过期商品" => .45   
    case "未过期商品" => .77
    case _ => 1.0
}

可用模式匹配实现java中switch语句功能:

val status = 500
val message = status match {
    case 200 => "ok"
    case 400 => {
        println("ERROR - 调用的服务不正确")
        "error"
    }
    case 500 => {
        println("ERROR - 服务器遇到了问题")
        "error"
    }
 }

val day = "MON"
val kind = day match {
    case "MON" | "TUE" | "WED" | "THU" | "FRI" => "工作日"
    case "SAT" | "SUN" => "周末"
}

Scala强大的模式匹配机制,可以应用在switch语句、类型检查以及“析构”等场合。

def swithOps: Unit ={
    var sign = 0
    val ch: Char = '+'
    ch match {
        case '+' => sign = 1
        case '-' => sign = -1
        case _ => sign = 0
    }
    println("sign===> " + sign)
}

swithOps

上面代码中,case _模式对应于switch语句中的default,能够捕获剩余的情况。如果没有模式能匹配,会抛出MatchError。而且不像常见的switch语句,在一种模式匹配之后,需要使用break来声明分支不会进入下一个分支,在scala中并不需要这种break语句。

另外,match是表达式,不是语句,所以是有返回值的,故可将代码简化:

sign = ch match {
    case '+' => 1
    case '-' => -1
    case _ => 0
}
println("sign=====> "+ sign)

Scala中的任何表达式都是有返回值的,模式匹配也不例外,我们可以直接获取对应的返回值进行操作。如果不写case _的操作,匹配不上的话,会抛出相关异常:scala.MatchError。

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

一般情况下也就是将模式匹配当做java中的switch case来进行使用的。

如果在case关键字后跟着一个变量名,那么匹配的表达式会被赋值给那个变量。case _ 是这个特性的一个特殊情况,变量名是_。

// 将要进行匹配的值,赋值给case后面的变量,我们可以对变量进行各种操作
def caseOps4: Unit = {
    "Hello, wordl" foreach(c => println(
      c match {
        case ' ' => "space"
        case ch => "Char: " + ch
      }
    ))
}

caseOps4

基本模式匹配

下面的代码演示了基本模式匹配的使用:

def printNum(int: Int) {
    int match {
        case 0 => println("Zero")
        case 1 => println("One")
        case _ => println("more than one")
    }
}

printNum(0)
printNum(1)
printNum(2)

def fibonacci(in: Int): Int = in match {
    case 0 => 0
    case 1 => 1
    case n => fibonacci(n - 1) + fibonacci(n - 2)
}
fibonacci(10)

def fib1(in: Int): Int = in match {
    case 0 | -1 | -2 => 0
    case 1 => 1
    case n => fibonacci(n - 1) + fibonacci(n - 2)
}

// 条件守护
def fib2(in: Int): Int = in match {
    case n if n <= 0 => 0
    case 1 => 1
    case n => fib2(n - 1) + fib2(n - 2)
}

模式匹配和在case子句中使用if表达式

可以通过在case语句中添加if表达式来模拟OR子句。

object demo {
  def main(args: Array[String]): Unit = {
    println("\n模式匹配和在case子句中使用if表达式")

    // val fruitType = "水果布丁"
    // val fruitType = "杨桃"
    val fruitType = "随便"

    val tasteLevel = fruitType match {
      case fruit if fruit.contains("水果") || fruit.contains("布丁") => "肯定很美味"
      case "杨桃"  => "尝一尝"
      case _  => "可以尝一尝"
    }
    println(s"${fruitType}的味道 = $tasteLevel")
  }
}

匹配元组

object demo {
  def main(args: Array[String]): Unit = {
    val one = ("张三", "男", 25000.00)
    val two = ("李四", "男", 30000.00)
    val three = ("小美", "女", 18000.00)
    val allList = List(one, two, three)
    
    // 元组匹配
    val priceOfPlainDonut = allList.foreach { tuple => {
        tuple match {
          case ("张三", gender, salary) => println(s"张三, 性别=$gender, 薪资=$salary")
          case d if d._1 == "张三" => println(s"${d._1}, 性别=${d._2}, 薪资=${d._3}")
          case _ => None
        }
      }
    }

  }
}

执行以上代码,输出结果如下:

张三, 性别=男, 薪资=25000.0

以上模式匹配更优雅的写法:

object demo {
  def main(args: Array[String]): Unit = {
    val one = ("张三", "男", 25000.00)
    val two = ("李四", "男", 30000.00)
    val three = ("小美", "女", 18000.00)
    val allList = List(one, two, three)

    // 更优雅的写法
    allList.foreach {
      case ("张三", gender, salary) => println(s"张三, 性别=$gender, 薪资=$salary")
      case d if d._1 == "张三" => println(s"${d._1}, 性别=${d._2}, 薪资=${d._3}")
      case _ => None
    }
  }
}

匹配Option类型

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

    val title:Option[String] = Some("老板")
    // val title:Option[String] = None

    title match{
      case Some(a) => println(s"title是$a。")
      case None => println(s"没有title,屌丝一个。")
    }
  }
}

匹配Any类型

下面的代码演示了如何将模式匹配应用于Any类型的数据:

val anyList= List(1, "A", 2, 2.5, 'a')
for (m <- anyList) {
    m match {
        case i: Int => println("Integer: " + i)
        case s: String => println("String: " + s)
        case f: Double => println("Double: " + f)
        case other => println("other: " + other)
    }
}

使用模式匹配可以代替isInstanceOf和asInstanceOf来进行使用。相比使用isInstanceOf来判断类型,使用模式匹配更好。

def caseOps5: Unit = {
    def typeOps(x: Any): Int = {
      val result = x match {
        case i: Int => i
        case s: String => Integer.parseInt(s)
        case z: scala.math.BigInt => Int.MaxValue
        case c: Char => c.toInt
        case _ => 0
      }
      result
    }

    println(typeOps("12345") == 12345)

}

caseOps5

可以通过模式匹配进行类型匹配。例如:

object demo06 {
  def main(args: Array[String]): Unit = {
    println("\n按类型匹配模式")
    val priceOfFruit: Any = 2.50

    val priceType = priceOfFruit match {
      case price: Int => "Int"
      case price: Float => "Float"
      case price: Double => "Double"
      case price: String => "String"
      case price: Boolean => "Boolean"
      case price: Char => "Char"
      case price: Long => "Long"
    }
    println(s"水果价格类型 = $priceType")
  }
}

执行以上代码,输出结果如下:

按类型匹配模式
水果价格类型 = Double

下面的代码同样演示了怎样通过模式匹配判断变量的数据类型:

def test2(in: Any) = in match {
    case s: String => "字符串,长度是" + s.length
    case i: Int if i > 0 => "自然整数"
    case i: Int => "整数"
    case a: AnyRef => a.getClass.getName
    case _ => "null"
}

匹配List类型

也可以使用模式匹配来匹配数组、列表和元组:

def caseOps6: Unit = {
    val arr = Array(0, 1)
    
    arr match {
      // 匹配只有一个元素的数组,且元素就是0
      case Array(0) => println("0")
      
      // 匹配任何带有两个元素的数组,并将元素绑定到x和y
      case Array(x, y) => println(x + " " + y)
      
      // 匹配任何以0开始的数组
      case Array(0, _*) => println("0 ...")
      
      case _ => println("something else")
    }
}

caseOps6

下面的代码演示了如何将模式匹配应用于List类型的数据:

val x = 1
val rest = List(2,3,4)
x :: rest

// 模式匹配:即可以比较,也可以提取值
(x :: rest) match { // 注意创造和匹配之间的对称性
    case Nil => 0
    case x :: rest => println(x); println(rest)
}

下面的代码使用模式匹配对所有奇数求和:

def sumOdd(in: List[Int]): Int = in match {
    case Nil => 0
    case x :: rest if x % 2 == 1 => x + sumOdd(rest)
    case _ :: rest => sumOdd(rest)     // 忽略第1个元素
}

val list1 = List(2,3,4)
sumOdd(list1)

下面的代码匹配List中任意数量的元素:

def noPairs[T](in: List[T]): List[T] = in match {
    case Nil => Nil
    case a :: b :: rest if a == b => noPairs(a :: rest)     // 如果前两个元素相同,排除连续重复的元素
    case a :: rest => a :: noPairs(rest)  
}

noPairs(List(1,2,3,3,3,4,1,1))

模式匹配既可以匹配常量,也可以提取信息:

def ignore(in: List[String]): List[String] = in match {
    case Nil => Nil
    case _ :: "ignore" :: rest => ignore(rest)
    case x :: rest => x :: ignore(rest)
}

使用类测试/强制转换机制查找List[Any]中的所有字符串:

def getStrings(in: List[Any]): List[String] = in match {
    case Nil => Nil
    case (s: String) :: rest => s :: getStrings(rest)
    case _ :: rest => getStrings(rest)
}

模式匹配与case class

模式匹配也可以对两个case class进行匹配:

case class Person(name: String, age: Int, valid: Boolean)

val p = Person("张三", 45, true)

// 也可以
val m = new Person("李四", 24, true)

def older(p: Person): Option[String] = p match {
    case Person(name, age, true) if age > 35 => Some(name)
    case _ => None
}

older(p).get
older(p).getOrElse("匿名") 

// older(m).get     // 会出现异常
older(m).getOrElse("匿名") 

样例类(case class)的模式匹配

下面这个示例代码,演示了如何匹配多个case object:

trait Command

case object Start extends Command
case object Go extends Command
case object Stop extends Command
case object Whoa extends Command

def executeCommand(cmd: Command) = cmd match {
      case Start | Go    => println("starting")
      case Stop | Whoa  => println("stopping")
      case default       => println("You gave me: " + default)  // 可访问default值  
}
    
executeCommand(Start)
executeCommand(Whoa)

模式匹配作为参数

模式匹配可作为参数传递给其他函数或方法,编译器会将模式匹配编译为函数:

val list = List("aa",123,"ss",456,"dd")

list.filter(a => a match {
    case s: String => true
    case _ => false
})

// 上面的代码可简化为
list.filter {
    case s: String => true
    case _ => false
}

《Spark原理深入与编程实战》