教材:快学Scala
chapter 14. 模式匹配和样例类 Pattern Matching and Case Classes
- The match expression is a better switch, without fall-through.
14.1 A Better Switch
// match is an expression
sign = ch match {
case '+' => 1
case '-' => -1
case _ => 0 // default
}
// guards
ch match {
...
case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10)
}
// type patterns 不需要isInstanceOf/asInstanceOf
obj match {
case x: Int => x
case s: String => Integer.parseInt(s)
case _: BigInt => Int.MaxValue // BigInt类型的对象
case BigInt => -1 // Class类型的BigInt对象
// 匹配发生在运行期,JVM中的泛型类型信息是被擦掉的,所以不能用Map[Int, Int]
case m: Map[_, _] => m
case _ => 0
}
// array
// 原理:Array.unapplySeq(arr)产出一个序列的值
arr match {
case Array(0) => "0"
case Array(x, y) => x + " " + y
case Array(0, _*) => "0 ..."
case _ => "..."
}
// list
lst match {
case 0 :: Nil => "0"
case x :: y :: Nil => x + " " + y
case 0 :: tail => "0 ..."
case _ => "..."
}
// tuple
pair match {
case (0, _) => "0 ..."
case (x, 0) => x + " 0"
case _ => "..."
}
// re
// 原理:pattern.unapplySeq("99 bottles")
val pattern = "([0-9]+) ([a-z]+)".r
"99 bottles" match {
case pattern(num, item) => println(num, item) // (99,bottles)
}
14.8 for表达式中的模式
在for推导式for (... <- ...)
中使用带变量的模式
for ((k, v) <- System.getProperties() if v == "") ...
失败的匹配将被安静地忽略
for ((k, "") <- System.getProperties()) ...
14.9 Case Classes
abstract class Amount // 将一类case class定义到同一个抽象类中方便匹配
case class Dollor(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object Nothing extends Amount // 样例对象 不带()
amt match {
case Dollor(v) => "$" + v
case Currency(_, u) => "Oh noes, I got " + u
case Nothing => ""
}
- case class的几个特性
构造器中的每个参数都为val
提供apply方法构造实例,不用new
提供unapply方法用于模式匹配
自动生成toString
equals
hashCode
copy
方法
val amt = Currency(30, "EUR")
val amt2 = amt.copy(unit = "CHN") // 可以用带名参数copy的同时修改某些属性值
- case语句的中置表示法 Infix Notation in case Clauses
条件:unapply方法返回一个pair结果都可以用中置表示法
amt match { case a Currency u => ... }
等价于
amt match { case Currency(a, u) => ... }
- 例子:List的实现
abstract class List
case object Nil extends List
case class ::[E](head: E, tail: List[E]) extends List[E]
lst match { case h :: t => ... }
等同于
lst match { case ::(h, t) => ... }
将调用::.unapply(lst)
- 匹配嵌套结构
abstract class Item
case class Article(desc: String, price: Double) extends Item
case class Bundle(desc: String, discount: Double, items: Item*) extends Item
// Item* 表示后面有>=0个Item参数
// 构造实例
val item = Bundle("Father's day special", 20.0,
Article("Scala for impatient", 39.95),
Bundle("Anchor Distillery Sampler", 10.0,
Article("Old Potrero", 79.95),
Article("Junipero", 32)
),
Multiple(2, Bundle("xxx", 10, Article("yyy", 23))), // 26
Multiple(2, Article("yyy", 47)), // 94
Multiple(5, Multiple(4, Article("zzz", 1))) // 20
)
item match {
case Bundle(_, _, Article(desc, _), _*) => println(desc) // 匹配第一个article的描述
case Bundle(_, _, art @ Article(_, _), rest @ _*) => println(art.desc) // 同上,用@绑定到变量
case Bundle(_, _, Bundle(_, _, art @ Article(_, _), rest @ _*), rest2 @ _*) => println(art.desc)
}
def price(it: Item): Double = it match {
case Article(_, p) => p
case Bundle(_, discount, it @ _*) => it.map(price(_)).sum - discount
}
优点:代码更精简;不需要new;有免费的toString
equals
hashCode
copy
缺点:若需要增加一种新的Item,对所有的match语句都要修改,一点都不OOP(enrage OO purists)
price函数在这里应该定义为每个Item子类各自实现的函数更合适
所以case class更适用于makeup不会改变的结构,即确保已经列出了所有可能的case class的选择
- 相同参数的case class实例它们是等效的,因此case class也叫value class(值类)
val c1 = Currency(10, "EUR")
val c2 = Currency(10, "EUR")
c1 == c2 // res81: Boolean = true
14.14 密封类 Sealed Classes
- 目的:用case class做模式匹配时,想让编译器帮你确保你已列出了所有可能的选择
sealed abstract class Amount
case class Dollor(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
- 效果:密封类的所有子类都必须在与该密封类相同的文件中定义
- 最佳实践:让同一组样例类都扩展自某个密封的类或者trait
14.16 Option Type
- Option:用样例类表示那种可能存在(Some样例类),也可能不存在(None样例对象)的值
- Map的
get
方法返回一个Option,还有getOrElse
方法 - 用for推导式自动忽略None值
for (score <- scores.get("Alice")) println(score)
scores.get("Alice").foreach(println(_))
练习答案
// source: C:\Program Files\Java\jdk1.8.0_101\src.zip
"case [^:\n]+:".r // 10540 matches across 680 files
"break;[ \t\n]+case [^:\n]+:".r // 3547 matches across 397 files
"break;[ \t\n]+default:".r // 469 matches across 229 files
val res = 4016.0 / 10540 // res: Double = 0.3810
def swap(pair: (Int, Int)) = pair match { case p: (Int, Int) => (p._2, p._1) }
def swap(s: Array[Int]) = s match {
case Array(x, y, _*) => s(0) = y; s(1) = x; s
case _ => s
}
sealed abstract class Item
case class Article(desc: String, price: Double) extends Item
case class Bundle(desc: String, discount: Double, items: Item*) extends Item
case class Multiple(amount: Int, item: Item) extends Item
def price(it: Item): Double = it match {
case Article(_, p) => p
case Bundle(_, discount, it @ _*) => it.map(price(_)).sum - discount
case Multiple(a, item) => a * price(item)
}
val item = Bundle("Father's day special", 20.0,
Article("Scala for impatient", 39.95),
Bundle("Anchor Distillery Sampler", 10.0,
Article("Old Potrero", 79.95),
Article("Junipero", 32)
),
Multiple(2, Bundle("xxx", 10, Article("yyy", 23))), // 26
Multiple(2, Article("yyy", 47)), // 94
Multiple(5, Multiple(4, Article("zzz", 1))) // 20
)
price(item) // res96: Double = 261.9
def leafSum(root: List[Any]): Int = root map { node: Any =>
node match {
case x: Int => x
case t: List[_] => leafSum(t)
}
} reduceLeft(_ + _)
sealed abstract class BinaryTree
case class Leaf(value: Int) extends BinaryTree
case class Node(left: BinaryTree, right: BinaryTree) extends BinaryTree
def leafSum1(root: BinaryTree): Int = root match {
case Leaf(v) => v
case Node(l, r) => leafSum(l) + leafSum(r)
}
sealed abstract class Tree extends BinaryTree
case class Leaf(value: Int) extends Tree
case class Node(children: Tree*) extends Tree
def leafSum2(root: Tree): Int = root match {
case Leaf(v) => v
case Node(ch @ _*) =>
var sum = 0
for (c <- ch) sum += leafSum(c)
sum
}
sealed abstract class EvalTree extends Tree
case class Leaf(value: Int) extends EvalTree
case class Node(op: Char, children: EvalTree*) extends EvalTree
def eval(root: EvalTree): Int = root match {
case Leaf(v) => v
case Node(op, ch @ _*) =>
var res = scala.collection.mutable.ArrayBuffer[Int]()
for (c <- ch) res += eval(c)
op match {
case '+' => res.foldLeft(0)(_ + _)
case '*' => res.foldLeft(1)(_ * _)
case '-' if (res.length > 1) => res.reduceLeft(_ - _)
case '-' if (res.length == 1) => -res(0)
case _ => 0
}
}
eval(Node('+', Node('*', Leaf(3), Leaf(8)), Leaf(2), Node('-', Leaf(5))))
def lstSum1(lst: List[Option[Int]]): Int = {
var sum: Int = 0
lst.map(e => e.foreach(sum += _))
sum
}
def lstSum2(lst: List[Option[Int]]): Int = {
var sum: Int = 0
for (Some(e) <- lst) sum += e
sum
}
def compose(f: (Double) => Option[Double], g: (Double) => Option[Double]) = (x: Double) => {
g(x) match {
case None => None
case Some(y) => f(y)
}
}