第5章 模式匹配
5.1 switch
与 default 等效的是捕获所有的 case_ 模式。如果没有模式匹配,抛出
MatchError,每个 case 中,不用 break 语句。
和 if 一样,match 也会返回值:

你可以在 match 中使用任何类型,而不仅仅是数字。
笔记:
def match1() = { var result = 0 val op : Char = '-' op match { case '+' => result = 1 case '-' => result = -1 case _ => result = 0 } println(result) //-1 } match1()
5.2 守卫
像 if 表达式一样,match 也提供守卫功能,守卫可以是任何 Boolean 条件:

笔记:
def match2() = { for(c <- "+-*/123"){ c match { case _ if Character.isDigit(c) => println("这是一个数字" + c) case '+' => println("字符为+号") case '-' => println("字符为-号") case '*' => println("字符为*号") case '/' => println("字符为/号") case _ => println("通配") } } } match2()
5.3 模式中的变量
如果 case 关键字后面跟着一个变量名,那么匹配的表达式会被赋值给那个变量。
val str = "+-3!" for (i <- str.indices) { var sign = 0 var digit = 0 str(i) match { case '+' => sign = 1 case '-' => sign = -1 case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10) case _ => } println(str(i) + " " + sign + " " + digit) }
Scala 中变量必须以小写字母开头,常量用大写字母,如果常量用小写字
母开头需要加反引号。
5.4 类型模式
可以匹配对象的任意类型,但是不直接匹配泛型类型,这样描述比较抽象,看下面的例子:
这样做的意义在于,避免了使用 isInstanceOf 和 asInstanceOf 方法。
笔记:
//类型模式 def match3() = { val a = 6 val obj = if(a == 1) 1 else if(a == 2) "2" else if(a == 3) BigInt(3) else if(a == 4) Map("aa" -> 1) else if(a == 5) Map(1 -> "aa") else if(a == 6) Array(1, 2, 3) else if(a == 8) Array("aa") else if(a == 7) Array("aa", 1) val r1 = obj match { case i : Int => i case s : String => s case bi : BigInt => bi case m1 : Map[String, Int] => println("Map[String, Int]") case m2 : Map[Int, String] => m2 case a1 : Array[Int] => a1 case a3 : Array[String] => a3 case a2 : Array[any] => a2 } println(r1 + ":" + r1.getClass.getName) } match3()
5.5 匹配数组、列表、元组
Array(0) 匹配只有一个元素且为 0 的数组。
Array(x,y) 匹配数组有两个元素,并将两个元素赋值为 x 和 y。
Array(0,_*) 匹配数组以 0 开始。
1)匹配数组:
for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0))) { val result = arr match { case Array(0) => "0" case Array(x, y) => x + " " + y case Array(0, _*) => "0 ..." case _ => "something else" } println(result) }
2)匹配列表
同样的方式可以应用于列表

3)匹配元组
集合元素通过匹配绑定到变量,这样的操作叫做“析构”。
笔记:
//匹配数组 def match4() = { for(arr <- Array(Array(0), Array(1, 0), Array(0, 1, 1), Array(1, 1, 0), Array(1, 1, 1, 0))){ arr match { case Array(0) => println("Array(0)") case Array(x, y) => println("Array(x, y)" + ":" + x + "," + y) case Array(x, y, z) => println("Array(x, y, z)" + ":" + x + "," + y + "," + z) case Array(1, arr @ _*) => println("Array(1, arr @ _*)" + ":" + arr.length) case _ => println("通配") } } } match4()
5.6 提取器
模式匹配,什么才算是匹配呢?即,case 中 unapply 方法返回 some 集合则为匹配成功,
返回 none 集合则为匹配失败。下面看几个例子:
1)unapply
-- 调用 unapply,传入 unmber
-- 接收返回值并判断返回值是None,还是 Some
-- 如果是 Some,则将其解开,并将其中的值赋值给 n(就是 case Square(n) 中的 n)
创建 object Square: object Square{ def unapply(z: Double): Option[Double] = Some(math.sqrt(z)) } 模式匹配使用: val number: Double = 36.0 number match { case Square(n) => println(s"square root of $number is $n") case _ => prinpln("nothing matched") }
到底什么时候用 unapply 什么时候用 unapplySeq,要看参数个数。
注意:如果要提取单个值,则应该返回一个目标类型的 Option,例如
Option[Int],而不是 Option[(Int)];无参数的提取器可以用于 boolean 检查。
5.7 变量声明中的模式
match 中每一个 case 都可以单独提取出来,意思是一样的,如下:
val (x, y) = (1, 2)
val (q, r) = BigInt(10) /% 3
val arr = Array(1, 7, 2, 9)
val Array(first, second, _*) = arr
println(first, second)
5.8 for表达式中的模式
在 for 表达式中使用提取器:
import scala.collection.JavaConverters._
for ((k, v) <- System.getProperties.asScala) println(k + " -> " + v) for ((k, "") <- System.getProperties.asScala) println(k) for ((k, v) <- System.getProperties.asScala if v == "") println(k)
笔记:
//for 循环中的模式匹配 import scala.collection.JavaConverters._ def match6() = { for ((k, v) <- System.getProperties.asScala) println(k + " -> " + v) for ((k, "") <- System.getProperties.asScala) println(k) for ((k, v) <- System.getProperties.asScala if v == "") println(k) } match6()
5.9 样例类
样例类首先是类,除此之外它是为模式匹配而优化的类,样例类用 case关键字进行声明:
1)样例类的创建
abstract class Amount case class Dollar(value: Double) extends Amount case class Currency(value: Double, unit: String) extends Amount case object Nothing extends Amount
2)当我们有一个类型为 Amount 的对象时,我们可以用模式匹配来匹配他
的类型,并将属性值绑定到变量:
for (amt <- Array(Dollar(1000.0), Currency(1000.0, "EUR"), Nothing)) { val result = amt match { case Dollar(v) => "$" + v case Currency(_, u) => "Oh noes, I got " + u case Nothing => "" } // Note that amt is printed nicely, thanks to the generated toString println(amt + ": " + result) }
笔记:
//样例类 def match7() = { for (e <- Array(Dollar(1000.0), Currency(1000, "EUR"))) { e match { case Dollar(v) => println(v) case Currency(k, v) => println(k + "," + v) } } } match7()
5.10 Copy方法和带名参数
copy 创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。
val amt = Currency(29.95, "EUR") val price = amt.copy(value = 19.95)
println(amt) //Currency(29.95,EUR) println(price) //Currency(19.95,EUR) println(amt.copy(unit = "CHF")) //Currency(29.95,CHF)
5.11 Case语句的中置(缀)表达式
什么是中置表达式?1 + 2,这就是一个中置表达式。如果 unapply 方法产出一个元组,你可以在 case 语句中使用中置表示
法。比如可以匹配一个 List 序列,可以如下表示:
List(1, 7, 2, 9) match { case first :: second :: rest => first + second + rest.length case _ => 0 }
笔记:
//Case语句的中置(缀)表达式 def match8() = { val list = List(1, 2, 3, 4, 5, 6) list match { case l1 :: l2 :: l3 => println(l1 + "," + l2 + "," + l3) } } match8() //1,2,List(3, 4, 5, 6)
5.12 匹配嵌套结构
样例类经常被用于嵌套结构。例如,某个商店售卖的物品,有时,会将
多个物品一起打着出售,我们有以下抽象:
1)创建样例类
abstract class Item case class Article(description: String, price: Double) extends Item case class Bundle(description: String, discount: Double, items: Item*) extends Item
2)匹配嵌套结构
val special = Bundle("Father's day special", 20.0, Article("Scala for the Impatient", 39.95), Bundle("Anchor Distillery Sampler", 10.0, Article("Old Potrero Straight Rye Whiskey", 79.95), Article("Junípero Gin", 32.95)))
3)将 descr 绑定到第一个 Article 的描述
val result1 = special match { case Bundle(_, _, Article(descr, _), _*) => descr }
println(result1)
4)通过@表示法将嵌套的值绑定到变量。_*绑定剩余 Item 到 rest
val result2 = special match { case Bundle(_, _, art @ Article(_, _), rest @ _*) => (art, rest) }
println(result2)
5)不使用 _* 绑定剩余 Item 到 rest
val result3 = special match { case Bundle(_, _, art @ Article(_, _), rest) => (art, rest) }
println(result3)
6)计算某个 Item 价格的函数,并调用
def price(it: Item): Double = { it match { case Article(_, p) => p case Bundle(_, disc, its @ _*) => its.map(price _).sum - disc } } price(special)
笔记:
//嵌套类的匹配 def match9() = { val sale = Bundle("秘籍", 10, Article("九阳神功", 40), Bundle("系列",20, Article("Java系列", 80), Article("小说系列", 30))) val result1 = sale match { case Bundle(_, _, Article(descr, _), temp @ _*) => descr + "," + temp } println(result1) //九阳神功,WrappedArray(Bundle(系列,20.0,WrappedArray(Article(Java系列,80.0), Article(小说系列,30.0)))) val result2 = sale match { case Bundle(_, _, art @ Article(_, _), rest @ _*) => (art, rest) } println(result2) //(Article(九阳神功,40.0),WrappedArray(Bundle(系列,20.0,WrappedArray(Article(Java系列,80.0), Article(小说系列,30.0))))) val result3 = sale match { case Bundle(_, _, art @ Article(_, _), rest) => (art, rest) } println(result3) //(Article(九阳神功,40.0),Bundle(系列,20.0,WrappedArray(Article(Java系列,80.0), Article(小说系列,30.0)))) def price(it: Item): Double = { it match { case Article(_, p) => p case Bundle(_, disc, its @ _*) => its.map(price(_)).sum - disc } } println(price(sale)) } match9()
5.13 密封类
如果想让 case 类的所有子类都必须在申明该类的相同的文件中定义,可
以将样例类的通用超类声明为 sealed,叫做密封类,密封就是外部用户不能在
其他文件中定义子类。
sealed abstract class TrafficLightColor case object Red extends TrafficLightColor case object Yellow extends TrafficLightColor case object Green extends TrafficLightColor
5.14 模拟枚举
for (color <- Array(Red, Yellow, Green)) println( color match { case Red => "stop" case Yellow => "hurry up" case Green => "go" }
)
笔记:
//模拟枚举 def match10() : Unit = { for (color <- Array(Red, Yellow, Green)) println( color match { case Red => "stop" case Yellow => "hurry up" case Green => "go" } ) } match10()
5.15 偏函数
偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算
val f: PartialFunction[Char, Int] = { case '+' => 1 case '-' => -1 } println(f('-')) //-1 println(f('+')) //1 println(f.isDefinedAt('0')) //false
map 和 collect:
//额外讨论 val list = List(1, 2, 3, 4, 5) val result1 = list.map(_ * 2) println(result1 + ", result1") //List(2, 4, 6, 8, 10) val result2 = list.collect{case x => x * 2} println(result2 + ", result2") //List(2, 4, 6, 8, 10) // val result3 = List(1, 2, 3, 4, 5, "haha").map{case i: Int => i * 2} // println(result3 + ", result3") //scala.MatchError val result4 = List(1, 2, 3, 4, 5, "haha").collect{case i: Int => i * 2} println(result4 + ", result2") //List(2, 4, 6, 8, 10)
再深入探讨一点点:
我们定义一个将 List 集合里面数据 +1 的偏函数
val f1 = new PartialFunction[Any, Int] { def apply(any: Any) = any.asInstanceOf[Int] + 1 def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false } val rf1 = List(1, 3, 5, "seven") collect f1 //collect 会调用 isDefinedAt 函数再执行 apply 方法,而 map 会直接执行 apply println(rf1) //List(2, 4, 6)
如上功能,等同于:
def f2: PartialFunction[Any, Int] = { case i : Int => i + 1 } val rf2 = List(1, 3, 5, "seven") collect(f2) println(rf2) //List(2, 4, 6)