12 Scala 模式匹配

模式匹配

1. match

1.1 基本介绍

  1. Scala 中的模式匹配类似于 Java 中的 switch 语法,但是更加强大。
  2. 模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Javadefault 语句。

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 的细节和注意事项

  1. 如果所有 case 都不匹配,又没有写 case _ 分支,那么会抛出 MatchError
  2. 可以在 match 中使用其它类型,而不仅仅是字符。
  3. => 后面的代码块到下一个 case, 是作为一个整体执行,可以使用 {} 扩起来,也可以不扩。

2. 守卫

2.1 基本介绍

  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 基本介绍

  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 基本介绍

  1. 可以匹配对象的任意类型,这样做避免了使用 isInstanceOfasInstanceOf 方法 。

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 基本介绍

  1. Array(0) 匹配只有一个元素且为 0 的数组。

  2. Array(x,y) 匹配数组有两个元素,并将两个元素赋值为 x 和 y。当然可以依次类推 Array(x,y,z) 匹配数组有3个元素的等等…

  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 基本介绍

  1. case 中对象的 unapply 方法(对象提取器)返回 Some 集合则为匹配成功。
  2. 返回 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 中的元素的个数是否是三个,如果是三个,则把三个元素分别取出,赋值给 firstsecondthird

9. 变量声明中的模式

9.1 基本介绍

  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 基本介绍

  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 基本介绍

  1. 样例类仍然是类。
  2. 样例类用 case 关键字进行声明。
  3. 样例类是为模式匹配而优化的类。
  4. 构造器中的每一个参数都成为 val ——除非它被显式地声明为 var(不建议这样做)。
  5. 在样例类对应的伴生对象中提供 apply 方法让你不用 new 关键字就能构造出相应的对象 。
  6. 提供 unapply 方法让模式匹配可以工作。
  7. 将自动生成 toStringequalshashCodecopy 方法(直接生成,供程序员使用)。
  8. 除上述外,样例类和其他类完全一样。可以添加方法和字段,扩展它们。

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 基本介绍

  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 元进行设计

  • 能够统计出所有捆绑商品打折后的最终价格

  1. 创建样例类:

    //设计样例类 
    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
    
  2. 匹配嵌套结构( Bundle 的对象):

    val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book(" 《围城》", 30)))
    
  3. 补充了三个知识点:

    // 知识点 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) 
    }
    
  4. 最终代码:

    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. 密封类

  1. 如果想让 case 类的所有子类都必须申明该类的相同源文件中定义,可以将样例类的通用超类声明为 seales ,这个超类称之为密封类。
  2. 密封就是不能在其他文件中定义子类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值