模式匹配
文章目录
1. match
1.1 基本介绍
Scala
中的模式匹配类似于Java
中的switch
语法,但是更加强大。- 模式匹配语法中,采用
match
关键字声明,每个分支采用case
关键字进行声明,当需要匹配时,会从第一个case
分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case
都不匹配,那么会执行case _
分支,类似于Java
中default
语句。
1.2 案例
val oper = '-'
val n1 = 20
val n2 = 10
var res = 0
oper match {
case '+' => res = n1 + n2
case '-' => res = n1 - n2
case '*' => res = n1 * n2
case '/' => res = n1 / n2
case _ => println("oper error")
}
- 如果匹配成功, 则 执行
=>
后面的代码块。 - 匹配的顺序是从上到下,匹配到一个就执行对应的代码。
=>
后面的代码块 不要写break
,会自动的退出match
。- 如果一个都没有匹配到,则执行
case _
后面的代码块。
1.3 match 的细节和注意事项
- 如果所有
case
都不匹配,又没有写case _
分支,那么会抛出MatchError
。 - 可以在
match
中使用其它类型,而不仅仅是字符。 =>
后面的代码块到下一个case
, 是作为一个整体执行,可以使用{}
扩起来,也可以不扩。
2. 守卫
2.1 基本介绍
- 如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫 。
2.2 案例
for (ch <- "+-3!") { //是对"+-3!" 遍历
var sign = 0
var digit = 0
ch match {
case '+' => sign = 1
case '-' => sign = -1
// 如果 case 后有 条件守卫即 if ,那么这时的 _ 不是表示默认匹配 , 而是表示忽略传入的 ch
case _ if ch.toString.equals("3") => digit = 3
case _ if (ch > 1110 || ch < 120) => println("ch > 10")
case _ => sign = 2
}
}
3. 模式中的变量
3.1 基本介绍
- 如果在
case
关键字后跟变量名,那么match
前表达式的值会赋给那个变量 。
3.2 案例
val ch = 'U'
ch match {
case '+' => println("ok~")
// 下面 case mychar 含义是 mychar = ch
case mychar => println("ok~" + mychar)
case _ => println ("ok~~")
}
4. 类型匹配
4.1 基本介绍
- 可以匹配对象的任意类型,这样做避免了使用
isInstanceOf
和asInstanceOf
方法 。
4.2 案例
val num = 8
// 根据类型来匹配
// match 有返回值
val result = num match {
case a: Int => a
case b: Map[String, Int] => "对象是一个字符串-数字的 Map 集合"
case c: Map[Int, String] => "对象是一个数字-字符串的 Map 集合"
case d: Array[String] => "对象是一个字符串数组"
case e: Array[Int] => "对象是一个数字数组"
case f: BigInt => Int.MaxValue
case _ => "啥也不是"
}
5. 匹配数组
5.1 基本介绍
-
Array(0)
匹配只有一个元素且为 0 的数组。 -
Array(x,y)
匹配数组有两个元素,并将两个元素赋值为 x 和 y。当然可以依次类推Array(x,y,z)
匹配数组有3个元素的等等… -
Array(0,_*)
匹配数组以 0 开始。
5.2 案例
val arrs = Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0))
for (arr <- arrs ) {
val result = arr match {
case Array(0) => "0"
case Array(x, y) => x + "=" + y
case Array(0, _*) => "以 0 开头和数组"
case _ => "什么集合都不是"
}
}
6. 匹配列表
for (list <- Array(List(0), List(1, 0), List(88), List(0, 0, 0), List(1, 0, 0))) {
val result = list match {
case 0 :: Nil => "0"
case x :: y :: Nil => x + " " + y
case 0 :: tail => "0 ..."
case x :: Nil => x
case _ => "something else"
}
}
7. 匹配元组
for (pair <- Array((0, 1), (1, 0), (10, 30), (1, 1), (1, 0, 2))) {
val result = pair match {
case (0, _) => "0 ..."
case (y, 0) => y
case (x, y) => (y, x)
case _ => "other"
}
}
8. 对象匹配
8.1 基本介绍
case
中对象的unapply
方法(对象提取器)返回Some
集合则为匹配成功。- 返回
None
集合则为匹配失败。
8.2 案例1
val num: Double = Square(5.0)
num match {
// 当匹配到 case Square(n)时,调用 Square 的 unapply(z:Double), 其中 z 就是 num
// 如果对象提取器 unapply(z: Double)返回的是 Some(6),则表示匹配成功,同时将 6 赋给 Square(n)的n
// 果对象提取器 unapply(z: Double) 返回的是 None ,则表示匹配不成功
case Square(n) => println("匹配成功 n=" + n)
case _ => println("nothing matched")
}
object Square{
def unapply(z: Double): Option[Double] = {
Some(math.sqrt(z))
// None
}
def apply(z: Double): Double = z * z
}
8.3 案例2
val namesString = "Alice,Bob,Thomas" //字符串
namesString match {
case Names(first, second, third) => {
println("the string contains three people's names")
println(s"$first $second $third")
}
case _ => println("nothing matched")
}
object Names {
//当构造器是多个参数时,就会触发这个对象提取器
def unapplySeq(str: String): Option[Seq[String]] = {
if (str.contains(",")) Some(str.split(","))
else None
}
}
- 当
case
后面的对象提取器方法的参数为多个,则会默认调用def unapplySeq()
方法。 - 如果
unapplySeq
返回是Some
,获取其中的值,判断得到的sequence
中的元素的个数是否是三个,如果是三个,则把三个元素分别取出,赋值给first
,second
和third
。
9. 变量声明中的模式
9.1 基本介绍
match
中每一个case
都可以单独提取出来,意思是一样的。
9.2 案例
val (x, y, z) = (1, 2, "hello")
println("x=" + x) // 1
val (q, r) = BigInt(10) /% 3 // 说明 q = BigInt(10) / 3 r = BigInt(10) % 3
val arr = Array(1, 7, 2, 9)
val Array(first, second, _*) = arr // 提出 arr 的前两个元素
println(first, second) // 1 7
10. for 表达式中的模式
10.1 基本介绍
for
循环也可以进行模式匹配。
10.2 案例
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for ((k, v) <- map) { // 出来三个 key-value ("A"->1), ("B"->0), ("C"->3)
println(k + " -> " + v)
}
for ((k, 0) <- map) { //说明 : 只遍历出 value =0 的 key-value ,其它的过滤掉
println(k + " --> " + 0)
}
for ((k, v) <- map if v == 0) { // 意义同上
println(k + " ---> " + v)
}
11. 样例类
11.1 快速入门
abstract class Amount
case class Dollar(value: Double) extends Amount //样例类
case class Currency(value: Double, unit: String) extends Amount //样例类
case object NoAmount extends Amount //样例类
11.2 基本介绍
- 样例类仍然是类。
- 样例类用
case
关键字进行声明。 - 样例类是为模式匹配而优化的类。
- 构造器中的每一个参数都成为
val
——除非它被显式地声明为var
(不建议这样做)。 - 在样例类对应的伴生对象中提供
apply
方法让你不用new
关键字就能构造出相应的对象 。 - 提供
unapply
方法让模式匹配可以工作。 - 将自动生成
toString
、equals
、hashCode
和copy
方法(直接生成,供程序员使用)。 - 除上述外,样例类和其他类完全一样。可以添加方法和字段,扩展它们。
11.3 案例1
for (amt <- Array(Dollar2(1000.0), Currency2(1000.0, "RMB"), NoAmount2)) {
val result = amt match {
case Dollar2(v) => "$" + v // $1000.0
case Currency2(v, u) => v + " " + u // 1000.0 RMB
case NoAmount2 => "" // ""
}
println(amt + ": " + result)
}
abstract class Amount2
case class Dollar2(value: Double) extends Amount2 //样例类
case class Currency2(value: Double, unit: String) extends Amount2 //样例类
case object NoAmount2 extends Amount2 //样例类
11.4 案例2
//样例类的 copy 方法和带名参数
//copy 创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。
val amt = new Currency3(3000.0,"RMB")
val amt2 = amt.copy() // 克隆,创建的对象和 amt 的属性一样
println("amt2.value" + amt2.value + " amt2.unit= " + amt2.unit)
val amt3 = amt.copy(value = 8000.0)
println(amt3)
val amt4 = amt.copy(unit = "美元")
abstract class Amount3
case class Dollar3(value: Double) extends Amount3 //样例类
case class Currency3(value: Double, unit: String) extends Amount3 //样例类
case object NoAmount3 extends Amount3 //样例类
12. case 语句的中置表达式
12.1 基本介绍
- 如果
unapply
方法产出一个元组,可以在case
语句中使用中置表示法。
12.2 案例
List(1, 3, 5, 9) match {
//1.两个元素间::叫中置表达式,至少 first,second 两个匹配才行.
//2.first 匹配第一个 second 匹配第二个, rest 匹配剩余部分(5,9)
case first :: second :: rest => println(first + " " + second + " "+ rest.length + " " + rest)
case _ => println("匹配不到...")
}
13. 匹配嵌套结构
13.1 案例
现在有一些商品,请使用 Scala 设计相关的样例类,完成商品捆绑打折出售。要求 :
-
商品捆绑可以是单个商品,也可以是多个商品
-
打折时按照折扣 x 元进行设计
-
能够统计出所有捆绑商品打折后的最终价格
-
创建样例类:
//设计样例类 abstract class Item // 项 case class Book(description: String, price: Double) extends Item //Bundle 捆 , discount: Double 折扣 , item: Item* case class Bundle(description: String, discount: Double, item: Item*) extends Item
-
匹配嵌套结构(
Bundle
的对象):val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book(" 《围城》", 30)))
-
补充了三个知识点:
// 知识点 1 - 使用 case 语句,得到 "漫画" val res = sale match { //如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有 case Bundle(_, _, Book(desc, _), _*) => desc } // 知识点 2 - 通过@表示法将嵌套的值绑定到变量。_*绑定剩余 Item 到 rest val res2 = sale match { //如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有 case Bundle(_, _, art @ Book(_, _), rest @ _*) => (art, rest) } // 知识点 3 - 不使用_*绑定剩余 Item 到 rest val res3 = sale match { //如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有 case Bundle(_, _, art3 @ Book(_, _), rest3) => (art3, rest3) }
-
最终代码:
def price(it:Item): Double = { it match { case Book(_,p) => p case Bundle(_,disc,its @ _*) => its.map(price).sum - disc } } println("price=" + price(sale)) // 120
14. 密封类
- 如果想让
case
类的所有子类都必须申明该类的相同源文件中定义,可以将样例类的通用超类声明为seales
,这个超类称之为密封类。 - 密封就是不能在其他文件中定义子类。