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])是必要的,因为在创建数组时 数组的类型是确定的,例如: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)
  }
}

你可能感兴趣的:(Scala 模式匹配)