模式匹配
Scala 中的模式匹配类似于java中的 switch 语法, 但是更加强大
模式匹配语法中, 使用 match
关键字声明, 每个分支采用 case
关键字进行声明, 当需要匹配是,会在第一个case
分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断.
如果所有 case
都不匹配, 那么会执行 case _
分支, 类似于 java 中的 default
语句
1.1基本应用
ex1: 输入1,2,3 会打印对应的颜色
object Color {
def main(args: Array[String]): Unit = {
//从键盘获取数字
val i: Int = StdIn.readInt()
i match {
case 1 => println("red")
case 2=> println("green")
case 3=> println("yellow")
case _=> println("您的输入有误")
}
}
}
说明:
- 如果所有
case
都不匹配,那么会执行case _ 分支,类似于 Java 中default
语句 - 如果所有
case
都不匹配,又没有写case _
分支,那么会抛出异常 - 每个
case
中,不用break
语句,自动中断case
- 可以
在match
中使用其它类型(任意类型),而不仅仅是字符 =>
等价于 javaswtich
的:
=>
后面的代码块到下一个case
, 是作为一个整体执行,可以使用{}
扩起来,也可以不括
ex2 : 加减乘除
object MatchDemo2 {
def main(args: Array[String]): Unit = {
var a1 = 10
var a2 = 20
var operChar = "*"
val res = operChar match {
case "+" => a1 + a2
case "-" => a1 - a2
case "*" => a1 * a2
case "/" => a1 / a2
case _ => "你的运算符负不正确"
}
println("res = " + res)
}
}
1.2 守卫
想要表达某个范围的数据, 就需要在匹配模式匹配中增加条件"守卫"
object MatchDemo3 {
def main(args: Array[String]): Unit = {
for (ch <- "+-3&%") {
var digit = 0
val sign = ch match {
case '+' => 1
case '-' => -1
case _ if Character.isDigit(ch) => digit = Character.digit(ch, 10)
case _ => 0
}
println("ch = " + ch)
println("sign = " + sign)
println("digit = " + digit)
println("---------")
}
}
}
1.3 变量和常量
如果在case
关键字后跟变量名,那么 match
前表达式的值会赋值给那个变量
object MatchVar {
def main(args: Array[String]): Unit = {
var ch = 0
'z' match {
case 'a' => println("a...")
case 'b' => println("b...")
case 'c' => println("c...")
case ch => println("ch=" + ch)
}
}
}
注意
- 如果
case ch
匹配成功了, 后面即使跟上case _
也没有用 - 可以把
case _
看成case 变量名
的特例 , 把_
看成一个变量来对待
但是:
按照约定, Scala 期望模式变量名都以小写字母开头, 而常量名则是大写字母(只要首字母大写也可以)。
如果你使用大写字母的名称,Scala 将会在作用域范围内查找常量。
但是,如果使用的是非大写字母的名称,它将只假设其是一个模式变量—-在作用域范围内任何具有相同非大写字母的名称都将会被忽略。
在下面的代码中,我们定义了一个和字段具有相同名称的模式变量, 但是这段代码将不会给出我们想要的结果—–模式变量隐藏了(Sample 类的)max
字段。
object PatternTest {
def main(args: Array[String]): Unit = {
val sample = new Sample
sample.process(1000)
}
}
class Sample {
val max = 10000
def process(input: Int): Unit = {
input match {
case max => println(s"You matched max")
}
}
}
说明:
-
你会发现 scala 把
max
推断为模式变量, 而不是模式常量. -
解决办法:
-
办法1: 明确指定作用域:
this.max
-
办法2: 使用反引号类给 scala 提示.
case `max` => .....
-
办法3: 把
max
变成大写:MAX
-
1.4 模式匹配
object MatchType {
def main(args: Array[String]): Unit = {
val list = List(1, 3.2, "sa",true)
var result = list match {
case a: List[Int] => println("...Int类型...")
case b : List[String] => println("...string...")
case c :List[Double] =>println("...double...")
case d:List[Boolean] => println("...boolean...")
}
println(result)
}
}
说明:
- 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错
- 如果类型匹配成功之后会把
s
的值赋值给case
后面跟着的变量, 而不需要做类型的转换. - 对数组来说提供数组元素中的类型是必要的,因为数组的底层类型确实是确定的.比如:
Array[Int]
这里的Int
是必须的 - 但是对集合类型比如
Map
, 提供泛型类型是无用的. 因为 Java 的"泛型擦除".Map[Int, String]和Map[Int, Int]
在匹配的时候没有区别. 所以应该使用通用的泛型:Map[_, _]
import scala.io.StdIn
object MatchType {
def main(args: Array[String]): Unit = {
var s: Any = Map("a" -> 11, "b" -> 22)
var result = s match {
case d: Map[_, _] => "匹配到的是Map"
case _ => "啥都没有匹配到"
}
println(result)
}
}
1.5 匹配数组
object PatternDemo5 {
def main(args: Array[String]): Unit = {
// Array(1,2,3) == Array(1,2,3)
var arr1 = Array(1, 2, 3, 5, 6)
arr1 match {
//匹配三个元素,只能是三个元素,且为123
// case Array(1, 2, 3) => println("Array(1,2,3)")
//匹配四个元素,且前两个元素为1,2
// case Array(1, 2, _, _) => println("Array(1,2,_,_)")
//匹配四个元素,第三个元素用变量接收
// case Array(1, 2, a, _) => println("Array(1,2,_,_) " + a)
//匹配前两个元素为1,2,后面是什么都行
// case Array(1, 2, _*) => println("Array(1,2, _*) ")
//匹配前两个为1,2, 后面为一个数组
// 在未来 @ 会变成 :
// case Array(1, 2, rest@_*) => println("Array(1,2, rest@_*) " + rest.mkString(", "))
case Array(_, _, rest@_*) => println("Array(_,_, rest@_*) " + rest.mkString(", "))
case _ => println("no ")
}
}
}
1.6 匹配列表
def main(args: Array[String]): Unit = {
val arr: List[Int] = List(1, 2, 3, 5, 6)
val res = arr match {
//case List(1, 2) => "List(1, 2)"
//case List(1, _*) => "匹配以 1 开头的列表"
//case 1 :: 2 :: 3 :: Nil => "匹配List(1,2,3)"
case 1 :: abc => println(abc); "匹配以 1 开头的列表"
case _ => "啥也可没有匹配到"
}
println(res)
}
1.7 匹配元组
object MatchTouple {
def main(args: Array[String]): Unit = {
// val tup1:(Int , String)= (1,"niefeng")
val tup2:(Int , String , String)= (2,"bujingyun","chuchu")
tup2 match {
case (a,b,c) => println(s"a=$a,b=$b,c=$c")
}
}
}
1.8 对象匹配(提取器)
对象匹配,什么才算是匹配呢?,规则如下:
case
中对象的unapply
方法(提取器)返回some
集合则为匹配成功- 返回
None
集合则为匹配失败
备注:
Some
和None
都是Option
的子类型- 从
2.11.1
开始, scala 为了性能上的优势, 放松了对unapply
返回的值的类型的限制(不必必须是Option
). 返回的类型只要具有以下方法就可以:def isEmpty: Boolean
返回值:true
用来标记匹配成功,false
匹配失败def get: T
返回值: 表示提取到的值.
- 其实
Some
和None
均包含上面的两个方法. - 另外一种情况: 如果
unapply
返回一个Boolean
值, 则只表示匹配成功或者失败. 如果是true
则表示匹配成功, 但是不会提取到任意的值. - 不过, 大部分情况下, 我们还是把要提取的数据封装到
Some
中返回.
1. 提取单个对象
object MatchObj {
def main(args: Array[String]): Unit = {
val num = 100.0
num match {
case Square(z) => println("z =" +z)
}
}
}
object Square{
def apply(a:Double): Double = a * a
def unapply(arg: Double): Option[Double] = Some(math.sqrt(arg))
}
result: 10.0
执行流程说明:
- 在
case
匹配的过程中, 如果碰到这种(伴生对象(参数)
)的形式的时, 就会调用这个伴生对象的unapply
方法, 来提前对象. - 调用
unapply
的时候, 会把前面的匹配表达式(本例中的num
)传递给unapply
- 如果
unapply
返回的是Some
, 则表示匹配成功. 并unapply
的返回值赋值给伴生对象(参数)
中的参数(本例中的z
),z
其实就是提取到的对象 - 如果
unapply
返回的None
, 则表示匹配失败. - 继续下一个
case
的匹配…
2. 提取多个对象
object MatchObjs {
def main(args: Array[String]): Unit = {
val s = "yangguo,xiaolongnv"
val res = s match {
case Names(one,two) => println(s"成功! one =$one,two =$two")
case _ => println("匹配失败")
}
}
}
object Names{
def unapplySeq(str:String)={
if (str.contains(","))
Some(str.split(","))
else
None
}
}
执行过程说明:
case Names(one, two)
, 这里有 32个需要提取的结果, 所以会调用伴生对象.unapplySeq
方法.- 如果这个方法返回的是
Some
, 并且Some
中的序列有 2个值, 则匹配成功. 否则就匹配失败.
1.9 变量声明中的模式
在声明变量的时候,也可以使用模式匹配的方式. (在其他语言中叫做解构)
object VarMatch {
def main(args: Array[String]): Unit = {
var (a: Int, b: Int) = (10, 20)
println(s"a = $a, b=$b")
val (aa, bb) = BigInt(10) /% 3
println(s"aa = $aa, bb = $bb")
val arr = Array(100, 200, 300, 400)
val Array(c, _, d, _*) = arr //
println(s"c = $c, d = $d")
}
}
1.10 for
表达式中的模式
def main(args: Array[String]): Unit = {
val map = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 2)
// 直接将Map中的K-V遍历出来
for ((k, v) <- map) {
println(s"k = $k, v = $v")
}
println("--------------")
// 只遍历 v = 2的 k-v
for((k, 2) <- map) {
println(s"k = $k")
}
println("--------------")
// 也可以使用 守卫: 遍历v > 1的
for ((k, v) <- map if v > 1){
println(s"k = $k, v = $v")
}
}
1.11 样例类
案例1
object CaseClassDemo1 {
def main(args: Array[String]): Unit = {
val arr = Array(Dollar(1000), Currency(10000, "RMB"))
for (ele <- arr) {
val res = ele match {
case Dollar(v) => "$" + v
case Currency(v, u) => v + u
case _ => ""
}
println(res)
}
}
}
// 一个抽象类
abstract class Amount {}
// Dollar: 样例类 继承自 Amount
case class Dollar(value: Double) extends Amount {}
// Currency: 样例类
case class Currency(value: Double, unit: String) {}
案例2
copy
会创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。
object CaseClassDemo2 {
def main(args: Array[String]): Unit = {
val amt1: Currency = Currency(122.2, "美元")
// 复制一个与 amt1 完全相同的对象
val amt2: Currency = amt1.copy()
val amt3: Currency = amt1.copy(value = 222.2)
val amt4: Currency = amt1.copy(unit = "英镑")
println(amt1)
println(amt2)
println(amt3)
println(amt4)
}
}
案例3
1.12 case语句中的中置表示法
)
1.13 匹配嵌套结构
object MatchNest {
def main(args: Array[String]): Unit = {
val book1 = Book("九阳真经", 100)
val book2 = Book("葵花宝典", 120)
val bundle1 = Bundle("书籍", 20, book1, book2)
println(price2(book1))
println(price2(book2))
println(price2(bundle1))
}
/**
* 计算打包的销售的书的价格
*
* 方式1:
*/
def price(item: Item): Double = {
val money = item match {
case Book(_, price) => price
case Bundle(_, discount, items@_*) => {
var sum = -discount
for (elem <- items) {
sum += price(elem)
}
sum
}
case _ => 0
}
money
}
// 方式2
def price2(item: Item): Double = {
item match {
case Book(_, price) => price
case Bundle(_, discount, items@_*) => {
// 不用循环
// 把每本书的价格映射到新的序列中, 然后再求和
items.map(price2).sum - discount
}
case _ => 0
}
}
}
// 抽象类
abstract class Item
// 样式类 Book
case class Book(description: String, price: Double) extends Item
// 捆绑的样式类
case class Bundle(description: String, discount: Double, item: Item*) extends Item
1.4 密封类
package com.liuser.scala.note
object MatchSeal {
def main(args: Array[String]): Unit = {
val amounts = Array(Dollar1(100),Dollar1(200), new Currency1(100,"rmb"))
for (elem <- amounts) {
elem match {
case Dollar1(v) => println("values=" + v)
case Currency1(v,u)=>println("v=" +v ,"u="+u)
case _ => println("匹配失败")
}
}
}
}
//密封类
sealed abstract class Amount1{}
case class Dollar1(value:Double) extends Amount1{}
case class Currency1(value:Double,unit : String) extends Amount1{}
1.5 模拟枚举类
object EnumDemo {
def main(args: Array[String]): Unit = {
val seasons = Array(Spring, Summer, Autumn, Winter)
for (season <- seasons) {
val msg = season match {
case Spring => "春天"
case Summer => "夏天"
case Autumn => "秋天"
case Winter => "冬天"
case _ => "每在地球上"
}
println(msg)
}
}
}
sealed abstract class Season
case object Spring extends Season
case object Autumn extends Season
case object Summer extends Season
case object Winter extends Season