Scala的模式匹配机制
2017-02-08 16:02
38 查看
Scala强大的模式匹配机制,可以应用在switch语句、类型检查以及“析构”等场合。样例类对模式匹配进行了优化。
上面代码中,case _模式对应于switch语句中的default,能够捕获剩余的情况。如果没有模式能匹配,会抛出MatchError。而且不像常见的switch语句,在一种模式匹配之后,需要使用break来声明分支不会进入下一个分支。match是表达式,不是语句,所以是有返回值的,故可将代码简化:
match表达式中可以使用任何类型。模式总是从上往下进行匹配。
看代码就好,与if表达式的守卫相同作用:
经过我的尝试,在如果变量名是_,那么在=>后使用_是不行的。
在模式中使用变量可能会与常量冲突。
在上面的代码中,要如何判断Pi这个标志符是一个用来匹配的常量还是模式中的变量?规则是:变量比需要以小写字母开始。如果有常量是小写字母开头的,那么需要用反引号将常量名包起来:
相比使用isInstanceOf来判断类型,使用模式匹配更好。
在匹配类型时,需要使用一个变量名,否则就是使用对象本身来进行匹配了。
因为匹配是发生在运行期的,而且JVM中泛型的类型信息会被擦掉,因此不能使用类型来匹配特定的Map类型(大部分集合类型也都不可以吧):
但对于数组来说,类型信息是完好的,所以可以在Array上匹配。
下面的模式匹配,功能与上面的代码是一样的,不过将数组换成了列表。
与上面两个例子差不多,模式匹配也可以使用在元组上。注意到变量将会被绑定到这三种数据结构的不同部分上,这种操作被称为“析构”。
在变量声明中的模式对于返回对偶(更广一点也可以用在元组上吧?)的函数来说很有用。
样例类是种特殊的类,经过优化以用于模式匹配。
使用:
在声明样例类时,下面的过程自动发生了:
构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
提供unapply方法使模式匹配可以工作;
生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
除了上述之外,样例类和其他类型完全一样,方法字段等。
样例类的copy方法创建一个与现有对象相同的新对象。可以使用带名参数来修改某些属性:
这个特性的本意是要匹配序列。举例,List对象要么是Nil,要么是样例类::。所以可以:
多个中置表达式放在一起时会比普通的形式更加易读。
模式可以匹配到特定的嵌套:
上面的代码中descr这个变量被绑定到第一个Article的description。另外还可以使用@来将值绑定到变量:
下面是个使用了模式匹配来递归计算Item价格的函数。
实际应用
Option类型用来表示可能存在也可能不存在的值。样例子类Some包装了某个值,而样例对象None表示没有值。Option支持泛型。
更好的switch
12345678 | var sign = ...val ch: Char = ... ch match { case '+' => sign = 1 case '-' => sign = -1 case _ => sign = 0} |
1 2 3 4 5 | sign = ch match { case '+' => 1 case '-' => -1 case _ => 0 } |
守卫
看代码就好,与if表达式的守卫相同作用:123456 | ch match { case '+' => sign = 1 case '-' => sign = -1 case _ if Character.isDigit(ch) => digit = Character.digit(ch, 10) case _ => sign = 0} |
模式中的变量
如果在case关键字后跟着一个变量名,那么匹配的表达式会被赋值给那个变量。case _是这个特性的一个特殊情况,变量名是_。1 2 3 4 5 6 | "Hello, world" foreach { c => println ( c match { case ' ' => "space" case ch => "Char: " + ch } )} |
在模式中使用变量可能会与常量冲突。
12345 | import scala.math._x match { case Pi => ... ...} |
1 2 3 4 5 | import java.io.File._ str match { case `pathSeparator` => ... ... } |
类型模式
相比使用isInstanceOf来判断类型,使用模式匹配更好。123456 | obj match { case x: Int => x case s: String => Integer.parseInt(s) case _: BigInt => Int.MaxValue case _ => 0} |
1 2 3 4 | obj match { case _: BigInt => Int.MaxValue // 匹配任何类型为BigInt的对象 case BigInt => -1 // 匹配类型为Class的BigInt对象 } |
12 | case m: Map[String, Int] => ... // 不行case m: Map[_, _] => ... // 匹配通用的Map,OK |
匹配数组、列表和元组
1 2 3 4 5 6 | arr match { case Array(0) => "0" // 匹配包含0的数组 case Array(x, y) => x + " " + y // 匹配任何带有两个元素的数组,并将元素绑定到x和y case Array(0, _*) => "0 ..." // 匹配任何以0开始的数组 case _ => "something else" } |
123456 | lst match { case 0 :: Nil => "0" case x :: y :: Nil => x + " " + y case 0 :: tail => "0 ..." case _ => "something else"} |
提取器
在上一节中,使用模式匹配来对数组、列表和元组进行了匹配,在这个过程的背后的是提取器(extractor)机制。使用unapply来提取固定数量的对象,使用unapplySeq来提取一个序列。在前面的代码 case Array(0, x) => ...中, Array(0, x)部分实际上是使用了伴生对象中的提取器,实际调用形式是: Array.unapplySeq(arr)。根据Doc,提取器方法接受一个Array参数,返回一个Option。正则表达式是另一个适用提取器的场景。正则有分组时,可以用提取器来匹配分组:1 2 3 4 | val pattern = "([0-9]+) ([a-z]+)".r "99 bottles" match { case pattern(num, item) => ... } |
变量声明中的模式
在变量声明中的模式对于返回对偶(更广一点也可以用在元组上吧?)的函数来说很有用。123 | val (x, y) = (1, 2)val (q, r) = BigInt(10) /% 3 // 返回商和余数的对偶val Array(first, second, _*) = arr // 将第一和第二个分别给first和second |
for表达式中的模式
这一部分的内容多在介绍for表达式时提过了,不过当时并没有意识到使用的是模式。1 2 3 4 5 6 7 | import scala.collection.JavaConversions.propertiesAsScalaMap for ((k, v) <- System.getProperties()) // 这里使用了模式 println(k + " -> " + v) for ((k, "") <- System.getProperties()) // 失败的匹配会被忽略,所以只打印出值为空的键 println(k) |
样例类
样例类是种特殊的类,经过优化以用于模式匹配。1234567 | abstract class Amount// 继承了普通类的两个样例类case class Dollar(value: Double) extends Amountcase class Currency(value: Double, unit: String) extends Amount // 样例对象case object Nothing extends Amount |
1 2 3 4 5 | amt match { case Dollar(v) => "$" + v case Currency(_, u) => "Oh noes, I got " + u case Nothing => "" // 样例对象没有() } |
构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
提供unapply方法使模式匹配可以工作;
生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
除了上述之外,样例类和其他类型完全一样,方法字段等。
copy方法和带名参数
样例类的copy方法创建一个与现有对象相同的新对象。可以使用带名参数来修改某些属性:123 | val amt = Currency(29.95, "EUR")val price = amt.copy(values = 19.95)val price = amt.copy(unit = "CHF") |
case语句中的中置表示法
如果unapply方法产出一个对偶,则可以在case语句中使用中置表示法。对于有两个参数的样例类,可以使用中置表示法。1 | amt match { case a Currency u => ... } // 等于case Currency(a, u) |
1 | lst match { case h :: t => ... } // 等同于case ::(h, t),调用::.unapply(lst) |
匹配嵌套结构
这个解释起来有点绕。1 2 3 4 5 6 7 8 9 10 11 | abstarct class Item case class Article(description: String, price: Double) extends Item case class Bundle(description: String, price: Double, items: Item*) extends Item 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 Whisky", 79.95), Article("Junipero Gin", 32.95) ) ) |
1 | case Bundle(_, _, Article(descr, _), _*) => ... |
1 2 | // art被绑定为第一个Article,rest是剩余的Item序列 case Bundle(_, _, art @ Article(_, _), rest @ _*) => ... |
实际应用
1234 | def price(it: Item): Double = it match { case Article(_, p) => p case Bundle(_, disc, its @ _*) => its.map(price _).sum - disc} |
密封类
当使用样例类来做模式匹配时,如果要让编译器确保已经列出所有可能的选择,可以将样例类的通用超类声明为sealed。密封类的所有子类都必须在与该密封类相同的文件中定义。如果某个类是密封的,那么在编译期所有的子类是可知的,因而可以检查模式语句的完整性。让所有同一组的样例类都扩展某个密封的类或特质是个好的做法。模拟枚举
可以使用样例类来模拟枚举类型:1 2 3 4 5 6 7 8 9 10 | sealed abstract class TrafficLightColor case object Red extends TrafficLightColor case object Yellow extends TrafficLightColor case object Green extends TrafficLightColor color match { case Red => "stop" case Yellow => "hurry up" case Green => "go" } |
Option类型
Option类型用来表示可能存在也可能不存在的值。样例子类Some包装了某个值,而样例对象None表示没有值。Option支持泛型。1234 | scores.get("Alice") match { case Some(score) => println(score) case Nome => println("No score")} |
偏函数(L2)
被包在花括号内的一组case语句是一个偏函数。偏函数是一个并非对所有输入值都有定义的函数,是PartialFunction[A, B]类的一个实例,其中A是参数类型,B是返回类型。该类有两个方法:apply方法从匹配的模式计算函数值;isDefinedAt方法在输入至少匹配其中一个模式时返回true。1 2 3 4 | val f: PartialFunction[Char, Int] = { case '+' => 1; case '-' => -1 } f('-') // 返回-1 f.isDefinedAt('0') // false f('0') //抛出MatchError |
相关文章推荐
- java 正则 块转义,忽略大小写,匹配换行模式,匹配先前匹配的文本(解释正则运行机制)
- 快学Scala习题解答—第十四章 模式匹配和样例类
- scala基础语法-match模式匹配
- 【Scala】模式匹配和样本类
- Scala的模式匹配和条件类
- Scala By Example: Case 类与模式匹配 习题
- 第74讲:从Spark源码的角度思考Scala中的模式匹配
- Scala中的match(模式匹配)
- scala-简单的模式匹配
- Scala详解----------特征、模式匹配
- Scala School 笔记(三)--模式匹配与函数组合
- Scala的模式匹配本质是什么? -从Coursera的响应式编程说起
- scala学习八 模式匹配之领域语言
- scala简要:模式匹配
- scala模式匹配的使用
- java 正则 块转义,忽略大小写,匹配换行模式,匹配先前匹配的文本(解释正则运行机制)
- Scala学习——模式匹配和样例类
- scala模式匹配的使用
- Scala匹配模式-----序列匹配
- Spark源码的角度思考Scala中的模式匹配