【Scala】模式匹配和样本类

模式匹配

要理解模式匹配(pattern-matching),先把这两个单词拆开,先理解什么是模式(pattern),这里所的模式是数据结构上的,这个模式用于描述一个结构的组成。

我们很容易联想到“正则表达”里的模式,不错,这个pattern和正则里的pattern相似,不过适用范围更广,可以针对各种类型的数据结构,不像正则表达只是针对字符串。比如正则表达式里 "^A.*" 这个pattern 表示以A开头、后续一个或多个字符组成的字符串;List("A", _, _*) 也是个pattern,表示第一个元素是”A”,后续一个或多个元素的List。

match表达式的不同

match表达式可以看做是Java风格switch的泛化。当每个模式都是常量并且最后一个模式可以是通配的时候,Java风格的switch可以被自然地表达为match表达式。但有三点不同需要牢记:

1. match表达式始终以值作为结果,这是Scala表达式的特点
2. Scala的备选项表达式永远不会意外掉入到下一个分支。在C或其他类C语言中,每个分支末尾要显式使用break语句来退出switch。
3. 如果没有模式匹配,MatchError异常会被抛出。这意味着你必须始终确信所有的情况都考虑到,或者至少意味着可以添加一个默认情况什么事都不做

模式的种类

1、通配模式(_)匹配任意对象,它被用作默认的“全匹配(catch-all)”的备选项
2、常量模型仅匹配自身,任何字面量都可以用作常量
3、变量模式类似于通配模式,它可以匹配任意对象。与通配符(_)不同的是,Scala把变量绑定在匹配的对象上。

//这里,如果expr非零
//somethingElse变量将绑定对象expr,结果输出expr的值
expr match {
    case 0 => "zero"
    case somethingElse => "not zero: " + somethingElse
}

4、构造器模式提供了深度匹配(deep match),如果备选项是样本类,那么构造器模式首先检查对象是否为该备选项的样本类实例,然后检查对象的构造器参数是否符合额外提供的模式。
构造器模式不只检查顶层对象是否一致,还会检查对象的内容是否匹配内层的模式。由于额外的模式自身可以形成构造器模式,因此可以使用它们检查到对象内部的任意深度。

//某个商店售卖物品,有时物品捆绑在一起打折出售
abstract class Item
case class Product(description: String, price: Double) extends Item
case class Bundle(description: String, discount: Double, items: Item*) extends Item

def price(it: Item): Double = it match {
  case Product(_, p) => p
  case Bundle(_, disc, its @ _*) => its.map(price _).sum * (100-disc) /100
  //这里@表示将嵌套的值绑定到变量its
}


//测试
val bun1 = Bundle("Father's day special", 20.0, Product("Massager", 188.0))
val bun2 = Bundle("Appliances on sale", 10.0, Product("Haier Refrigerato, 3000.0),
                                               Product("Geli air conditionor",2000.0))

//商品组合1 八折结果
scala> price(bun1)
res5: Double = 150.4
//商品组合2 九折结果
scala> price(bun2)
res6: Double = 4500.0

5、序列模式可以像匹配样本类那样匹配如List或者Array这样的序列类型。

expr match {
    case List(0, _, _) => println("found it")
    case _ =>
}

//匹配不定长序列
expr match {
    case List(0, _*) => println("found it")
    case _ => 
}

6、元组模式匹配元祖
7、类型模式可以当做类型测试和类型转换的简易替代。

scala> def generalSize(x: Any) = x match {
     |   case s: String => s.length
     |   case m: Map[_, _] => m.size
     |   case _ => 1
     | }
generalSize: (x: Any)Int

scala> generalSize("abc")
res7: Int = 3

scala> generalSize(Map(1 -> 'a', 2 -> 'b'))
res8: Int = 2

scala> generalSize(Math.PI)
res9: Int = 1

样本类

带有case修饰符的类称为样本类(case class)。这种修饰符可以让Scala编译器自动为你的类添加一些句法上的便捷性。

  1. 样本类会添加与类名一致的工厂方法。你不用new关键字就可以创建这个类。
  1. 样本类参数列表中的所有参数隐式获得val前缀,因此它被当做字段维护。
  2. 编译器为样本类添加了方法toString、hashCode和equals的实现。

这些便捷性的代价就是必须写case修饰符并且样本类和对象都因为附加的方法及对于每个构造器参数添加了隐含的字段而变得大了一点。
样本类是一种特殊的类,它经过优化以被用于模式匹配。

封闭类

封闭类除了类定义所在的文件之外不能再添加任何新的子类。其用于模式匹配的另外一个作用是,当你用样本类来做模式匹配是,你可能想让编译器帮你确保你已经列出了所有可能的选择。为了达到这个目的,你需要将样本类的通用超类声明为sealed。如果你使用继承自封闭类的样本类做匹配,编译器将通过通知警告信息标识出缺失的模式组合。
举个例子:

sealed abstract class Amount

case class Dollar(value: Double) extends Amount
case class Euro(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount

def describe(a: Amount): String = a match {
    case Dollar(_) => "Dollar"
    case Euro(_) => "Euro"
}

//这里会出现编译器警告
//warning: match may not be exhaustive.
//It would fail on the following input: Currency(_, _)
//       def describe(a: Amount): String = a match {
//                                         ^
//describe: (a: Amount)String

如果想要让编译器不进行警告提示的话,需要给匹配的选择器表达式添加@unchecked注解。
像是这样def describe(a: Amount): String = (a: @unchecked) match {
如果某个类是封闭的,那么在编译器所有子类就是可知的,因而编译器可以检查模式语句的完整性。让所有(同一组)样本类都扩展某个封闭类或特质是个好的做法。

Option类型

标准类库中的Option类型用样本类来表示那种可能存在、也可能不存在的值。可以是Some(value)的形式,其中value是实际的值;也可以是None对象,代表缺失的值。
Scala集合类的某些标准操作会产生可选值。例如Scala的Map的get方法会发现了指定键的情况下产生Some(value),在没有找到指定键的时候产生None。
举例如下:

scala> val capitals = Map("France" -> "Paris",
     | "Japan" -> "Tokyo", "China" -> "Beijing")
capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo, China -> Beijing)

scala> capitals get "France"
res2: Option[String] = Some(Paris)

scala> capitals get "North Pole"
res3: Option[String] = None

样本类None的形式比空字符串的意图更加清晰,比使用null来表示缺少某值的做法更加安全。
Option支持泛型。举例来说,Some(Paris)的类型为Option[String]。

分离可选值最通用的办法是通过模式匹配的方式,举例如下:

scala> def showCapital(x: Option[String]) = x match {
     |   case Some(s) => s
     |   case None => "?"
     | }
showCapital: (x: Option[String])String

scala> showCapital(capitals get "Japan")
res5: String = Tokyo

scala> showCapital(capitals get "France")
res6: String = Paris

scala> showCapital(capitals get "China")
res7: String = Beijing

scala> showCapital(capitals get "North Pole")
res8: String = ?

Scala鼓励对Option的使用以说明值是可选的。这种处理可选值的方式有若干超越Java的优点。

Option[String]类型的变量是可选的String,这比String类型的变量或可能有时是null来说更加明显
使用可能为null而没有检查是否为null的变量产生的编程错误在Scala里变为类型错误,即如果变量是Option[String]类型的,而你打算当做String使用,这样不会编译通过。

参考资料

话说模式匹配(1): 什么是模式?

转载请注明作者Jason Ding及其出处
GitCafe博客主页(http://jasonding1354.gitcafe.io/)
Github博客主页(http://jasonding1354.github.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354进入我的博客主页

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容