【转】Scala学习——模式匹配和样例类

原文链接 http://nerd-is.in/2013-09/scala-learning-pattern-matching-and-case-classes/

原文发表于:http://nerd-is.in/2013-09/scala-learning-pattern-matching-and-case-classes/

Scala强大的模式匹配机制,可以应用在switch语句、类型检查以及“析构”等场合。

样例类对模式匹配进行了优化。

更好的switch

1

2

3

4

5

6

7

8

var sign = ...

val ch: Char = ...

ch match {

case ‘+‘ => sign = 1

case ‘-‘ => sign = -1

case _ => sign = 0

}

上面代码中,case _模式对应于switch语句中的default,能够捕获剩余的情况。

如果没有模式能匹配,会抛出MatchError。而且不像常见的switch语句,

在一种模式匹配之后,需要使用break来声明分支不会进入下一个分支。

match是表达式,不是语句,所以是有返回值的,故可将代码简化:

1

2

3

4

5

sign = ch match {

case ‘+‘ => 1

case ‘-‘ => -1

case _ => 0

}

match表达式中可以使用任何类型。模式总是从上往下进行匹配。

守卫

看代码就好,与if表达式的守卫相同作用:

1

2

3

4

5

6

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

}

)}

经过我的尝试,在如果变量名是_,那么在=>后使用_是不行的。

在模式中使用变量可能会与常量冲突。

1

2

3

4

5

import scala.math._

x match {

case Pi => ...

...

}

在上面的代码中,要如何判断Pi这个标志符是一个用来匹配的常量还是模式中的变量?

规则是:变量比需要以小写字母开始。如果有常量是小写字母开头的,那么需要用反引号将常量名包起来:

1

2

3

4

5

import java.io.File._

str match {

case `pathSeparator` => ...

...

}

类型模式

相比使用isInstanceOf来判断类型,使用模式匹配更好。

1

2

3

4

5

6

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对象

}

因为匹配是发生在运行期的,而且JVM中泛型的类型信息会被擦掉,

此不能使用类型来匹配特定的Map类型(大部分集合类型也都不可以吧):

1

2

case m: Map[String, Int] => ...  // 不行

case m: Map[_, _] => ...  // 匹配通用的Map,OK

但对于数组来说,类型信息是完好的,所以可以在Array上匹配。

匹配数组、列表和元组

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"

}

下面的模式匹配,功能与上面的代码是一样的,不过将数组换成了列表。

1

2

3

4

5

6

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) => ...

}

变量声明中的模式

在变量声明中的模式对于返回对偶(更广一点也可以用在元组上吧?)的函数来说很有用。

1

2

3

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)

样例类

样例类是种特殊的类,经过优化以用于模式匹配。

1

2

3

4

5

6

7

abstract class Amount

// 继承了普通类的两个样例类

case class Dollar(value: Double) extends Amount

case 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方法创建一个与现有对象相同的新对象。可以使用带名参数来修改某些属性

1

2

3

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)

这个特性的本意是要匹配序列。举例,List对象要么是Nil,要么是样例类::。所以可以:

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, _), _*) => ...

上面的代码中descr这个变量被绑定到第一个Article的description。另外还可以使用@来将值绑定到变量:

1

2

// art被绑定为第一个Article,rest是剩余的Item序列

case Bundle(_, _, art @ Article(_, _), rest @ _*) => ...

下面是个使用了模式匹配来递归计算Item价格的函数。

实际应用

1

2

3

4

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支持泛型。

1

2

3

4

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



本章练习参考

时间: 2024-11-17 08:15:45

【转】Scala学习——模式匹配和样例类的相关文章

快学Scala(14)--模式匹配和样例类

更好的switch def main(args: Array[String]): Unit = { var sign: Int = 0 val ch: Char = '+' val color = Color.BLACK sign = ch match { case '+' => 1 case '-' => -1 case _ => 0 } color match { case Color.RED => ; case Color.BLACK => ; case _ =>

好程序员大数据教程分享Scala系列之模式匹配和样例类

好程序员大数据教程分享Scala系列之模式匹配和样例类1.样例类在Scala中样例类是一中特殊的类,样例类是不可变的,可以通过值进行比较,可用于模式匹配.定义一个样例类:1.构造器中每一个参数都是val,除非显示地声明为var 2.伴生对象提供apply ,让你不使用new关键字就能构造出相应的对象case class Point(x: Int, y: Int)创建样例类对象:val point = Point(1, 2)val anotherPoint = Point(1, 2)val yet

从零学scala(七)集合、模式匹配和样例类

一:集合 主要的集合特质 scala集合中重要的特质: Trait(Iterable) Trait(Seq) Trait(Set) Trait(Map) Trait(IndexedSeq) Trait(SoredSet) Trait(SoredMap) Seq是一个有先后次序的值的序列,比如数组和列表.IndexSeq允许我们通过下表快速访问元素,ArrayBuffer是带下标的,但是链表不是. Set是一个没有先后次序的值的序列,SortedSet中,元素以排过序的顺序被访问. Map是一组(

模式匹配和样例类

模式匹配是类似switch-case特性,但更加灵活:也类似if-else,但更加简约. 1 def fibonacci(i : Any) : Int = i match { 2 case 0 => 0 3 case 1 => 1 4 case n : Int if (n > 1) => fibonacci(n -1) + fibonacci(n - 2) 5 case _ => 0 6 } 7 8 println(fibonacci(3)) 9 println(fibona

快学Scala第14章----模式匹配和样例类

本章要点 match表达式是一个更好的switch,不会有意外掉入到下一个分支的问题. 如果没有模式能够匹配,会抛出MatchError.可以用case _ 模式来避免. 模式可以包含一个随意定义的条件,称作守卫. 你可以对表达式的类型进行匹配:优先选择模式匹配而不是isInstanceOf/asInstanceOf. 你可以匹配数组.元组和样例类的模式,然后将匹配到的不同部分绑定到变量. 在for表达式中,不能匹配的情况会被安静的跳过. 样例类继承层级中的公共超类应该是sealed的. 用Op

【Scala篇】--Scala中Trait、模式匹配、样例类、Actor模型

一.前述 Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大. 模式匹配机制相当于java中的switch-case. 使用了case关键字的类定义就是样例类(case classes),样例类是种特殊的类. Actor相当于Java中的多线程. 二.具体阐述 trait特性 1.概念理解 Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大. 与接口不同的是,它还可以定义属性和方法的实现. 一般情况下Scala的类可以继承多个Tra

Scala笔记整理(七):模式匹配和样例类

[TOC] 可以用到switch语句 1.Scala强大的模式匹配机制,可以应用在switch语句.类型检查以及"析构"等场合. def swithOps: Unit ={ var sign = 0 val ch: Char = '+' ch match { case '+' => sign = 1 case '-' => sign = -1 case _ => sign = 0 } println("sign===> " + sign) }

scala模式匹配与样例类

样本类:添加了case的类便是样本类.这种修饰符可以让Scala编译器自动为这个类添加一些语法上的便捷设定.如下: 1.添加与类名一致的工厂方法.也就是说,可以写成Var("x")来构造Var对象.    2.样本类参数列表中的所有参数隐式获得了val前缀,因此它被当作字段维护. 3.编译器为这个类添加了方法toString,hashCode和equals等方法. 模式匹配: match对应Java里的switch,但是写在选择器表达式之后.即: 选择器 match {备选项}. 一个

好程序员大数据教程Scala系列之样例类_Option_偏函数

好程序员大数据教程Scala系列之样例类_Option_偏函数,在Scala中Option类型样例类用来表示可能存在或也可能不存在的值(Option的子类有Some和None).Some包装了某个值,None表示没有值. object?OptionDemo {??def?main(args: Array[String]) {????val?map = Map("a"?-> 1, "b"?-> 2)????val?v = map.get("b&q