Scala模式匹配的用法

1 switch

  • Scala 中的模式匹配类似于 Java 中的 switch 语法,但是更加强大。
  • 模式匹配语法中, 采用 match 关键字声明, 每个分支采用 case 关键字进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。
  • 如果所有 case 都不匹配,那么会执行case _ 分支,类似于 Java 中 default 语句。
    如果没有任何模式匹配成功, 那么会抛出 MatchError
  def main(args: Array[String]): Unit = {
    val v = "/"
    v match{
      case "+" => println("++++++++++++")
      case "-" => println("------------")
    }
  }
Exception in thread "main" scala.MatchError: / (of class java.lang.String)
	at com.nefu.scala.chaptor08.Scala01_Match1$.main(Scala01_Match1.scala:11)
	at com.nefu.scala.chaptor08.Scala01_Match1.main(Scala01_Match1.scala)

  • 每个 case 中,不用 break 语句。
  • 可以在 match 中使用任何类型,而不仅仅是数字。
  • Jav的switch语句支持的类型:
byte short int char enum String
// Java
int i = 1;
switch ( i ) {
case 0 :
break;
case 1 :
break;
default :
break
}
  • 下面是Scala:
变量 match {
	case _: 1	//默认可以放在最上面
	case ex:Exception => println(ex.getMessage)
	
}
  • 异常处理中的模式匹配:
    try {
      val r = 10 / 0  //0不能作为除数
    } catch {
      case ex: ArithmeticException=> println("捕获了除数为零的算数异常")
      case ex: Exception => println("捕获了异常")
    } finally {
      // 无论有没有出现异常都要执行的代码
    }
  }

2 守卫

  • 如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫 守卫条件更灵活 增加了一个条件判断
  val v = "a"
    v match {
      case "+" => println("++++++++++++")
      case "-" => println("------------")
      case _ if(v.equals("a")) => println("aaaaaaaaaaaaa")
      case _ => println("Nothing...")
    }

3 模式中的变量

  • 如果在 case 关键字后跟变量名,那么 match 前表达式的值会赋给那个变量。
    val v = "a"
    v match {
      case "+" => println("++++++++++++")
      case "-" => println("------------")
      case ch => println("length = " + ch.length)
      case _ if(v.equals("a")) => println("aaaaaaaaaaaaa")
      case _ => println("Nothing...")

4 类型匹配

  • 可以匹配对象的任意类型, 这样做避免了使用 isInstanceOfasInstanceOf 方法。
    val a = 6
    val obj = if(a == 1) 1
    else if(a == 2) "2"
    else if(a == 3) BigInt(3)
    else if(a == 4) Map("aa" -> 1)
    else if(a == 5) Map(1 -> "aa")
    else if(a == 6) Array(1, 2, 3)
    else if(a == 7) Array("aa", 1)
    else if(a == 8) Array("aa")

    //在类型的模式匹配中不支持泛型 Map[String, Int]和Map[Int, String]一样
    val r1 = obj match {
      case x: Int => x  //类型匹配
      case s: String => s.toInt
      case _: BigInt => Int.MaxValue //这个下划线是变量名 ???
      case m: Map[String, Int] => "Map[String, Int]类型的 Map 集合"
      case m: Map[Int, String] => "Map[Int,String]类型的 Map 集合"
      case a: Array[String] => "It's an Array[String]"  //String[]
      case a: Array[Int] => "It's an Array[Int]"        //int[]
      case _ => 0
    }
    println(r1 + ", " + r1.getClass.getName
length = 1
It's an Array[Int], java.lang.String
  • 注: 类型不能直接匹配泛型类型(类型匹配不考虑泛型) 数组的“泛型”其实是类型,所以生效。

5 匹配数组、列表、元组

  • Array(0) 匹配只有一个元素且为 0 的数组。
  • Array(x,y) 匹配数组有两个元素,并将两个元素赋值为 x 和 y。
  • Array(0,_*) 匹配数组以 0 开始。

5.1 匹配数组

    for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))) {
      val result = arr match {
        case Array(0) => "0"
        case Array(x, y) => x + " " + y
        case Array(x, y, z) => x + " " + y + " " + z
        case Array(0, _*) => "0..."
        case _ => "something else"
      }
      println(result)
0
1 0
0 1 0
1 1 0
something else

5.2 匹配列表

  • 与匹配数组相似,同样可以应用于列表


      for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0))) {
        val result = list match {
          case 0 :: Nil => "0"
          case x :: y :: Nil => x + " " + y //(1, 0)
          case 0 :: tail => "0 ..." //list(0)上面已经满足了
          case _ => "something else"
        }
        println(result)
      }
0
1 0
0 ...
something else

5.3 匹配元组

  • 同样可以应用于元组
    for (pair <- Array((0, 1), (1, 0), (1, 1))) {
      val result = pair match {
        case (0, _) => "0 ..."
        case (y, 0) => y + " 0"
        case _ => "neither is 0"
      }
      println(result)
    }
0 ...
1 0
neither is 0

6 对象匹配

  • 对象匹配,什么才算是匹配呢?即, case 中对象的 unapply 方法返回 some 集合则为匹配成功,返回 none 集合则为匹配失败。

6.1 unapply

  • 调用 unapply,传入 number
  • 接收返回值并判断返回值是 None,还是 Some
  • 如果是 Some,则将其中的值赋值给 n(就是 case Square(n)中的 n)
  • 请参考伴生对象的 apply 方法理解
object Square {
  def unapply(z: Double): Option[Double] = {
    Some(math.sqrt(z))
  }
}




//unapply 提取器
  val number: Double = 36.0
  number match {
    case Square(n) => println(n)
    case _ => println("nothing matched")
  }
6.0

6.2 unapplySeq

  • 调用 unapplySeq,传入 namesString
  • 接收返回值并判断返回值是 None,还是 Some
  • 如果是 Some, 获取其中的值
  • 判断得到的 sequence 中的元素的个数是否是三个
  • 如果是三个,则把三个元素分别取出,赋值给 firstsecondthird
//创建 object Names:
object Names {
def unapplySeq(str: String): Option[Seq[String]] = {
if (str.contains(",")) Some(str.split(","))
else None
}
}
//模式匹配使用:
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") //s 表示后面的字符串中有表达式,要获取对应的值
}
case _ => println("nothing matched")
}

7 变量声明中的模式

  • match 中每一个 case 都可以单独提取出来,意思是一样的,如下:
    val (x, y) = (1, 2)
    val (q, r) = BigInt(10) /% 3
    val arr = Array(1, 7, 2, 9)
    val Array(first, second, _*) = arr
    println(first, second)
(1,7)

8 for 表达式中的模式

val map = Map("A"->1, "B"->0, "C"->3)
for ( (k, v) <- map ) {
println(k + " -> " + v)
}
for ((k, 0) <- map) {
println(k + " --> " + 0) // for 中匹配会自动忽略失败的匹配
}
for ((k, v) <- map if v == 0) {
println(k + " ---> " + 0)
}

9 样例类

  • 样例类首先是类,除此之外它是为模式匹配而优化的类,样例类用 case 关键字进行声明:

9.1 样例类的创建

abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object Nothing extends Amount

9.2 当我们有一个类型为 Amount 的对象时,我们可以用模式匹配来匹配他的类型,并将属性值绑定到变量:

for (amt <- Array(Dollar(1000.0), Currency(1000.0, "EUR"), Nothing)) {
val result = amt match {
case Dollar(v) => "$" + v
case Currency(_, u) => u
case Nothing => ""
}
println(amt + ": " + result)
}
  • 当你声明样例类时,如下几件事情会自动发生:
    • 构造器中的每一个参数都成为 val——除非它被显式地声明为 var(不建议这样做)
    • 在伴生对象中提供 apply 方法让你不用 new 关键字就能构造出相应的对象,比如Dollar(29.95)Currency(29.95, "EUR")
    • 提供 unapply 方法让模式匹配可以工作
    • 将生成 toString、 equals、 hashCode 和 copy 方法——除非显式地给出这些方法的定义。除上述外,样例类和其他类型完全一样。你可以添加方法和字段,扩展它们。

10 case 语句的中置(缀)表达式

  • 什么是中置表达式? 1 + 2,这就是一个中置表达式。 如果 unapply 方法产出一个元组,你可以在 case 语句中使用中置表示法。比如可以匹配一个 List 序列。
List(1, 7, 2, 9) match {
case first :: second :: rest => println(first + second + rest.length)
case _ => 0
}

11 匹配嵌套结构

  • 操作原理类似于正则表达式
  • 比如某一系列商品想捆绑打折出售

11.1 创建样例类

abstract class Item
case class Article(description: String, price: Double) extends Item
case class Bundle(description: String, discount: Double, item: Item*) extends Item
  def main(args: Array[String]): Unit = {
    val a = Article("aaa",1)
    println(a.description)
    println(a.price)
  }
aaa
1.0

11.2 匹配嵌套结构

val sale = Bundle("书籍", 10, Article("漫画", 40), Bundle("文学作品", 20, Article(" 《阳关》", 80), Article("《围城》 ", 30))

11.3 将 descr 绑定到第一个 Article 的描述

val result1 = sale match {
case Bundle(_, _, Article(descr, _), _*) => descr
}
println(result1)

11.4 通过@表示法将嵌套的值绑定到变量。 _*绑定剩余 Item 到 rest

val result2 = sale match {
case Bundle(_, _, art @ Article(_, _), rest @ _*) => (art, rest)
}
println(result2)

11.5 不使用_*绑定剩余 Item 到 rest

val result3 = sale match {
case Bundle(_, _, art @ Article(_, _), rest) => (art, rest)
}
println(result3)

12.6 计算某个 Item 价格的函数,并调用

def price(it: Item): Double = {
it match {
case Article(_, p) => p
case Bundle(_, disc, its@_*) => its.map(price _).sum - disc
}
}
println(SwitchBaseSyllabus.price(sale))

12 封闭类

  • 如果想让 case 类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明为 sealed, 这个超类称之为密封类,密封就是不能在其他文件中定义子类。
abstract sealed class Item
//样例类 样本类
case class Article(description: String, price: Double) extends Item{}
case class Bundle(description: String, discount: Double, item: Item*) extends Item
case object MyNothing extends Item

13 模拟枚举

  • 样例类可以模拟出枚举类型

13.1 创建样例类

abstract class TrafficLightColor
case object Red extends TrafficLightColor
case object Yellow extends TrafficLightColor
case object Green extends TrafficLightColor

13.2 模拟枚举

for (color <- Array(Red, Yellow, Green))
println(
color match {
case Red => "stop"
case Yellow => "slowly"
case Green => "go"
})

14 偏函数

  • 将集合中的数字加 1 并返回新的集合
    val list = List(1, 2, 3, 4)
    def add( obj : Any ) : Int = {
      obj match {
        case i:Int => i+1
      }
    }
    val list1 = list.map( add )
    println(list1)
List(2, 3, 4, 5)
  • 如果这个时候,集合的元素有字符串,会出现什么情况?
val list = List(1, 2, 3, 4, "ABC")
def add( obj : Any ) : Int = {
obj match {
case i:Int => i+1
}
}
val list1 = list.map( add )
println(list1)
// 此时程序执行时会发生错误 matcherror
  • 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择
  • 将包在大括号内的一组 case 语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出范围的值会忽略(未必会忽略,这取决于你打算怎样处理)
  • 偏函数在 Scala 中是一个特质 PartialFunction
val list = List(1, 2, 3, "abcd")
    // 定义一个将 List 集合里面数字加 1 的偏函数
    // 构建特质的实现类
    // [Any, Int]是泛型,第一个表示参数类型,第二个表示返回参数
    val addOne= new PartialFunction[Any, Int] {
      def apply(any: Any) = any.asInstanceOf[Int] + 1
      def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
    }
    //val list1 = list.map( addOne ) // // (X) map 函数不支持偏函数
    val list1 = list.collect(addOne)// OK collect 函数支持偏函数
    println(list1)
List(2, 3, 4)
  • 声明偏函数,需要重新特质中的方法,有的时候会略显麻烦,而 Scala 其实提供了简单的方法
 def f2: PartialFunction[Any, Int] = {
      case i: Int => i + 1 // case 语句可以自动转换为偏函数
    }
 val list1 = list.collect{case i:Int => i + 1}// OK collect 函数支持偏函数
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值