Scala 模式匹配

一、模式匹配基本使用

object PatternVarVal {
  def main(args: Array[String]): Unit = {
    val a = 10
    val b = 0
    val op = StdIn.readLine("请输入一个运算符:")
    val res = op match {
        case "+" => a + b
        case "-" => a - b
        case "*" => a * b
        case "/" if b != 0 => a / b     // 可以根据需要添加守卫
        case _ =>
    }
    println("res = " + res)
  }
}

说明:

  1. => 后的代码可以写多行且不用 { }
  2. 模式匹配也是表达式,所以它也可以赋值给一个变量
  3. case 匹配值 之后可以添加任何布尔条件的守卫
  4. 模式总是从上至下匹配,当所有 case 都不匹配,会执行 case _ 分支,如果没有写 case _ 分支,会在最后抛出 MatchError

二、模式中的变量

如果在 case 关键字后跟变量名,那么 match 前的表达式会赋值给这个变量

object MatchDemo4 {
  def main(args: Array[String]): Unit = {
    var ch = 0
    'z' match {
      case 'a' => println("a...")
      case 'b' => println("b...")
      case 'c' => println("c...")
      case ch => println("变量 ch = " + ch)
    }
  }
}

其实 case _ 就是 case 变量名 的特例, 你可以把`_`当做一个变量来对待,只是这个变量不再被使用了。
按照约定, Scala 期望模式变量名都以小写字母开头, 而常量名则是大写字母(只要首字母大写也可以)。
如果你使用大写字母的名称,Scala 将会在作用域范围内查找常量。

object PatternVarVal2 {
  def main(args: Array[String]): Unit = {
    val sample = new Sample
    sample.process(10)
  }
}
class Sample {
  val max = 10
  val Max = 10
  def process(input: Int): Unit = {
    // 1.指明作用域
    input match {
      case this.max => println(s"You matched this.max")
      case _ =>
    }
    // 2.使用首字母大写给 scala 提示
    input match {
      case Max => println(s"You matched Max")
      case _ =>
    }
    // 3.使用反引号类给 scala 提示
    input match {
      case `max` => println(s"You matched `max`")
      case _ =>
    }
    // 4.直接使用max会进入模式变量,将input的值赋值给max
    input match {
      case max => println(s"Yout matched " + max)
      case _ =>
    }
  }
}
--------------------输出
You matched this.max
You matched Max
You matched `max`
You matched 10

三、匹配类型

可以根据对象的类型进行匹配,避免了使用 isInstanceOf 和 asInstanceOf

object PatternType {
  def main(args: Array[String]): Unit = {
    val s: Any = Map("a" -> 11, 2 -> 22)
    val res:String = s match {
      case a: Int => println(a); "匹配到的是 Int"
      case b: String => println(b); "匹配到的是 String"
      case c: Array[Int] => "匹配到的是Aarray[Int]"
      case d: Map[_, _] => "匹配到的是Map[String, Int]"
      case _: BigInt => "匹配到的是 BigInt"
      case _ => "什么都没有匹配到"
    }
    println(res)
  }
}
----------输出
匹配到的是Map[String, Int]

说明:

  1. 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错
  2. 如果类型匹配成功之后会把 s 的值赋值给 case 后面跟着的变量, 而不需要做类型的转换
  3. 提供数组元素的泛型(Array[Int])是必要的,因为在创建数组时 <font color="fbbc05">数组的类型是确定的</font>,例如:Int[ ]、String[ ]
  4. 对于 集合类型比如 Map,提供泛型时无用的,因为 Java 的“泛型擦除”,Map[Int, Int] 和 Map[Int, String] 在匹配的时候没有区别,所以应该使用通用的泛型:Map[_, _]

四、匹配数组内容

匹配数组内容,可以在模式中使用 Array 表达式

object PatternArray {
  def main(args: Array[String]): Unit = {
    val arr: Array[Int] = Array(1, 20, 11, 1)
    val res = arr match {
        // 匹配只有2个元素,且元素值为(1, 2)的数组
      case Array(1, 2) => "Array(1, 2)" 
        // 匹配长度为3的数组,并把元素一次赋值给a,b,c
      case Array(a, b, c) => s"$a, $b, $c" 
        // case Array(1, _*) => "Array(1,...)"  // 匹配任何以1开头的数组
        // 匹配以1开头的数组,并把之后的所有元素放入b中,b是一个集和
      case Array(1, b@_*) => b ; 
        // 匹配长度为3的数组,第一个是1,最后一个也是1
      case Array(1, _, 1) => "1, _, 1"  

      // 匹配任意数组
      case Array(_*) => "匹配任意数组"
      case _ => "什么也没匹配到"
    }
    println(res)
  }
}
-----------输出
Vector(20, 11, 1)

五、匹配列表内容

匹配类别内容,可以在模式中使用 List 表达式,同时可以使用 List 专用符号 ::

object MatchList {
  def main(args: Array[String]): Unit = {
    val arr: List[Int] = List(1, 2, 3, 5, 6)
    val res = arr match {
      //case List(1, 2) => "List(1, 2)"
      //case List(1, _*) => "匹配以 1 开头的列表"
      //case 1 :: 2 :: 3 :: Nil => "匹配List(1,2,3)"
      case 1 :: abc => println(abc); "匹配以 1 开头的列表"
      case _ => "啥也可没有匹配到"
    }
    println(res)
  }
}

六、匹配元组内容

case 后可以直接使用 ( )进行元组模式的匹配

object MatchTuple {
  def main(args: Array[String]): Unit = {
    // val tup1: (Int, Int) = (10, 20)
    val tup1: (Int, Int, Int) = (10, 20, 30)

    tup1 match {
      // 匹配第一个是10的两个元素的元组
      // case (10, _) => println("(10, _)")
      // case (a, _, _) => println(a)
      // case (_, _, 30) => println("_, _, 30")
      case (a, b, c) => println(a + " " + b + " " + c)
      case _ => println("啥都不匹配")
    }
  }
}

七、匹配对象(提取器)

对象匹配的规则:
case 中的对象的 unapply 方法(提取器),返回 Some 集合则为匹配成功
返回 None 集合则为匹配失败
说明:

  1. Some 和 None 都是 Option 的子类
  2. Scala 从 2.11.1 版本开始,放松了对 unapply 返回的值的类型的限制(可以不是 Option)
    返回的类型只要满足两个方法:
    ​ isEmpty:Boolen 返回 false 代表匹配成功,true 匹配失败
    ​ get:T 若匹配成功则获取到具体匹配到的值
  3. 其实 Some 和 None 类中均包含 isEmpty 和 get 方法
  4. 但是大部分情况下,还是会把要提取的数据封装到 Some 中返回
    对象匹配提取单个对象
object PatternObj1 {
  def main(args: Array[String]): Unit = {
    val num: Double = 9
    // 1.进入匹配
    num match {
      case My(a) => println(a)  // 2. num 传入 object My      6. 匹配成功,r传回来,r=3.0 
      case _ => println("没有匹配到任何东西")
    }
  }
}
object My {
  def unapply(arg: Double): My = new My(arg)    // 3.以 num 为参数,创建 My 对象
}
class My(r: Double){
  def isEmpty = r < 0   // 4. num 传入到 r ,9 > 0 返回 false,匹配成功
  def get = math.sqrt(r)    // 5.获取 sqrt(r) 的值
}
----------输出
3.0

执行过程:

  1. 在 case 匹配的过程中, 如果碰到这种( 伴生对象(参数) )的形式的时, 就会调用这个伴生对象的 unapply 方法, 来提前对象。
  2. 调 unapply 的时候, 会把前面的匹配表达式(本例中的 num )传递给 unapply
  3. 如果 unapply 返回的是 Some(My 具有 Some 特性), 则表示匹配成功. 并 unapply 的返回值赋值给伴生对象 —— 本例中的 My(a) , a 其实就是提取到的对象。
    对象匹配提取序列
    unappply 只能提取单个对象,如果想提取多个对象(序列),需要 unapplySeq 方法
object PatternObj3 {
  def main(args: Array[String]): Unit = {
    val names= "lisi,za,ww,zhiling"
    names match {
      case Names(one, two, _*) => println(one, two)
    }
  }
}

object Names {
  def unapplySeq(s: String) = {
    if (s.length == 0) None
    else Some(s.split(","))
  }
}
-----------输出
(lisi,za)

执行过程说明:

  1. case Names(one, two, three) , 这里有 3 个需要提取的结果, 所以会调用 伴生对象.unapplySeq 方法。
  2. 如果这个方法返回的是 Some , 并且 Some 中的序列(集合)有 3 个值,则匹配成功. 否则就匹配失败。

八、模式匹配的应用场景

变量声明的模式: 在声明变量的时候,也可已使用模式匹配(在很多语言中叫做结构)

object VarMatch {
  def main(args: Array[String]): Unit = {
    var (a: Int, b: Int) = (10, 20)
    println(s"a = $a, b=$b")
      
    val (aa, bb) = BigInt(10) /% 3
    println(s"aa = $aa, bb = $bb")
      
    val arr = Array(100, 200, 300, 400)
    val Array(c, _, d, _*) = arr
    println(s"c = $c, d = $d")
  }
}

在 for 循环中的模式: 在 map 中的 key-value 也可使用模式匹配

object PatterUse {
    def main(args: Array[String]): Unit = {    
        val map = Map(1 -> 2, 2-> 3)
        for ((k, v) <- map) {
            println(v)
        }
    }
}

九、样例类

如果一个类用 case 来修饰, 这样的类就是样例类,样例类是一种特别的类, 经过优化被用于匹配模式。

object partternObj2 {
  def main(args: Array[String]): Unit = {
  
    val user = new User("lisi", 20)
    user match {
      case User(name, _) => println(name)
    }
    
    val User(name, _) = user
    println(name)
  }
}
/*object User {
  def unapply(arg: User): Option[(String, Int)] = Some((arg.name, arg.age))
}*/
// 使用 case 修饰类,类称为样例类,其中的 unapply 方法会自动声明
case class User(val name: String, val age: Int)
  1. 样例类会生成一系列的方法,比如:apply、unapply、toString、copy、hashcode、equals 等等。
  2. 样例类是为模式匹配而优化的类
  3. 构造器中的每一个参数都成为 val ——除非它被显式地声明为 var (不建议这样做)
  4. 在样例类对应的伴生对象中提供 apply 方法让你不用 new 关键字就能构造出相应的对象
  5. 提供 unapply 方法让模式匹配可以工作
  6. 将自动生成 toString、equals、hashCodecopy 方法

十、偏函数

定义一个偏函数: 用一对大括号括起来的一系列的 case 语句,就是一个偏函数,基础是模式匹配。

object PartialFunc {
  def main(args: Array[String]): Unit = {
    // foo函数参数中,传入的{}内的一系列 case 语句,就是一个偏函数
    val res = foo({
      case ((a, b), c) => b + c
    })
    println(res)
  }
  def foo(f: ((Int, Int), Int) => Int) = {
    f((1, 2), 10)
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 1.scala是匹配和java中的switch的区别: ...
    Coderlxl阅读 425评论 0 0
  • 样本类 case class 类前加上case修饰符,编译器会添加额外的语法,更好的支持模式匹配 添加与类名称相同...
    wangdy12阅读 1,005评论 0 0
  • 模式匹配 要理解模式匹配(pattern-matching),先把这两个单词拆开,先理解什么是模式(pattern...
    JasonDing阅读 1,360评论 0 1
  • Scala 的模式匹配是类似与正则匹配的的模式匹配,但是不仅仅如此,它还可以匹配对象的内在的构建形式. 模式匹配就...
    Tim在路上阅读 287评论 0 0
  • 模式匹配是Scala中非常有特色且强大的功能。模式匹配类似于Java中的swich case语法即对一个值进行条件...
    SunnyMore阅读 354评论 0 1