Scala 模式匹配详解

Posted hyunbar777

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala 模式匹配详解相关的知识,希望对你有一定的参考价值。

Scala



Scala 中的模式匹配类似于 Java 中的 switch 语法

 int i = 10
switch (i)
case 10 :
System.out.println("10");
break;
case 20 :
System.out.println("20");
break;
default :
System.out.println("other number");
break;

但是 scala 从语法中补充了更多的功能,所以更加强大。

1 基本语法

  • 模式匹配语法中,采用 match 关键字声明
  • 每个分支采用 case 关键字进行声明
  • 当需 要匹配时,会从第一个 case 分支开始
    • 如果匹配成功,那么执行对应的逻辑代码
    • 如果匹 配不成功,继续执行下一个分支进行判断。
    • 如果所有 case 都不匹配,那么会执行 case _分支, 类似于 Java 中 default 语句
 val a = 25
val b = 13

def matchDualOp(op: Char): Int = op match
case + => a + b
case - => a - b
case * => a * b
case / => a / b
case % => a % b
case _ => -1


println(matchDualOp(+))
println(matchDualOp(/))
println(matchDualOp(\\\\))
  • 说明:

(1)如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句,

若此时没有 case _ 分支,那么会抛出 MatchError

(2)每个 case 中,不需要使用 break 语句,自动中断 case

(3)match case 语句可以匹配任何类型,而不只是字面量。

(4)=> 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行

可以 使用括起来,也可以不括。

2 模式守卫

  • 说明

如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。

  • 案例实操


 def abs(num: Int): Int = 
num match
case i if i >= 0 => i
case i if i < 0 => -i



println(abs(67))

3 模式匹配类型

3.1 匹配常量

 def test(x: Any): String = x match 
case 1 => "Int one"
case "hello" => "String hello"
case true => "Boolean true"
case + => "Char +"
case _ => ""


println(test("hello"))
println(test(+))
println(test(0.3))
 String hello
Char +

3.2 匹配类型

需要进行类型判断时,可以使用前文所学的 isInstanceOf[T]和 asInstanceOf[T],也可使

用模式匹配实现同样的功能。

 def test(x: Any): String = x match 
case i: Int => "Int " + i
case s: String => "String " + s
//case list: List[String] => "List " + list
case array: Array[Int] => "Array[Int] " + array.mkString(",")
//泛型擦除
case m: List[_] => "List"+m
case a => "else: " + a


println(test(35))
println(test("hello"))
//泛型擦除
println(test(List("hi", "hello")))
//泛型擦除
println(test(List(2, 23)))
println(test(Array("hi", "hello")))
println(test(Array(2, 23)))
 Int 35
String hello
List List(hi, hello)
List List(2, 23)
else: [Ljava.lang.String;@6e8dacdf
Array[Int] 2,23

3.3 匹配数组

scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素

为 0 的数组。

 for (arr <- List(
Array(0),
Array(1, 0),
Array(0, 1, 0),
Array(1, 1, 0),
Array(2, 3, 7, 15),
Array("hello", 1, 30)
))
val result = arr match
case Array(0) => "0"
case Array(1, 0) => "Array(1, 0)"
// 匹配两元素数组
case Array(x, y) => "Array: " + x + ", " + y
case Array(0, _*) => "以0开头的数组"
case Array(x, 1, z) => "中间为1的三元素数组"
case _ => "else"


println(result)

 0
Array(1, 0)
以0开头的数组
中间为1的三元素数组
else
中间为1的三元素数组

3.4 匹配列表

方式一:

     for (list <- List(
    List(0),
    List(1, 0),
    List(0, 0, 0),
    List(1, 1, 0),
    List(88),
    List("hello")
    ))
    val result = list match
    case List(0) => "0"
    case List(x, y) => "List(x, y): " + x + ", " + y
    case List(0, _*) => "List(0, ...)"
    case List(a) => "List(a): " + a
    case _ => "else"

    println(result)

       0
      List(x, y): 1, 0
      List(0, ...)
      else
      List(a): 88
      List(a): hello

      方式二:

       val list = List(1, 2, 5, 7, 24)
      //val list = List(24)

      list match
      case first :: second :: rest =>
      println(s"first: $first, second: $second, rest: $rest")
      case _ => println("else")


       first: 1, second: 2, rest: List(5, 7, 24)

      3.5 匹配元组

         for (tuple <- List(
        (0, 1),
        (0, 0),
        (0, 1, 0),
        (0, 1, 1),
        (1, 23, 56),
        ("hello", true, 0.5)
        ))
        val result = tuple match
        case (a, b) => "" + a + ", " + b
        //是第一个元素是 0 的元组
        case (0, _) => "(0, _)"
        //是第二个元素是 1 的元组
        case (a, 1, _) => "(a, 1, _) " + a
        case (x, y, z) => "(x, y, z) " + x + " " + y + " " + z
        case _ => "else"

        println(result)

           a 12
          b 35
          c 27
          a 13
          a: 12
          b: 35
          c: 27
          a: 13

          3.6 匹配对象

           object test 
          def main(args: Array[String]): Unit =
          val student = Student("法外狂徒,张三", 18)

          // 针对对象实例的内容进行匹配
          val result = student match
          case Student("法外狂徒,张三", 18) => "法外狂徒,张三, 18"
          case _ => "Else"


          println(result)



          // 定义类
          class Student(val name: String, val age: Int)

          // 定义伴生对象
          object Student
          def apply(name: String, age: Int): Student = new Student(name, age)
          // 必须实现一个unapply方法,用来对对象属性进行拆解
          def unapply(student: Student): Option[(String, Int)] =
          if (student == null)
          None
          else
          Some((student.name, student.age))



          • ​val student = Student("法外狂徒,张三",11)​​,该语句在执行时,实际调用的是 Student 伴生对象中的apply 方法,因此不用 new 关键字就能构造出相应的对象
          • 当将 ​​Student("法外狂徒,张三", 11)​​写在 case 后时​​[case Student("法外狂徒,张三", 11) => "法外狂徒,张三, 18"]​​,会默认调用 unapply 方法(对象提取器),Student 作为 unapply 方法的参数,unapply 方法将 Student 对象的 name 和 age 属性提取出来,与 ​​Student("法外狂徒,张三", 11)​​中的属性值进行匹配
          • case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功,属性不一致,或返回 None,则匹配失败。
          • 若只提取对象的一个属性,则提取器为 ​​unapply(obj:Obj):Option[T]​
          • 若提取对象的多个属性,则提取器为 ​​unapply(obj:Obj):Option[(T1,T2,T3…)]​
          • 若提取对象的可变个属性,则提取器为 ​​unapplySeq(obj:Obj):Option[Seq[T]]​

          3.7 样例类

           object test 
          def main(args: Array[String]): Unit =
          val user = User("zhangsan", 18)

          // 针对对象实例的内容进行匹配
          val result = user match
          case User("zhangsan", 18) => "zhangsan, 18"
          case _ => "Else"


          println(result)



          // 定义样例类
          case class User(name: String, age: Int)
          • 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如:applyunapplytoString、equals、hashCode 和 copy。
          • 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。
          • 构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)

          查看字节码文件:

          User$:

           package com.duo;

          import scala.None.;
          import scala.Option;
          import scala.Serializable;
          import scala.Some;
          import scala.Tuple2;
          import scala.runtime.AbstractFunction2;
          import scala.runtime.BoxesRunTime;

          public final class User$ extends AbstractFunction2<String, Object, User>
          implements Serializable

          public static final MODULE$;

          static

          new ();


          public final String toString()

          return "User";
          public User apply(String name, int age) return new User(name, age);
          public Option<Tuple2<String, Object>> unapply(User x$0) return x$0 == null ? None..MODULE$ : new Some(new Tuple2(x$0.name(), BoxesRunTime.boxToInteger(x$0.age())));
          private Object readResolve() return MODULE$;
          private User$() MODULE$ = this;


          User:

          import scala.Function1;
          import scala.Option;
          import scala.Product;
          import scala.Product.class;
          import scala.Serializable;
          import scala.Tuple2;
          import scala.collection.Iterator;
          import scala.reflect.ScalaSignature;
          import scala.runtime.BoxesRunTime;
          import scala.runtime.ScalaRunTime.;
          import scala.runtime.Statics;

          @ScalaSignature(bytes="\\006\\001\\005....")
          public class User
          implements Product, Serializable

          private final String name;
          private final int age;

          public static Option<Tuple2<String, Object>> unapply(User paramUser)

          return User..MODULE$.unapply(paramUser);


          public static User apply(String paramString, int paramInt)

          return User..MODULE$.apply(paramString, paramInt);


          public static Function1<Tuple2<String, Object>, User> tupled()

          return User..MODULE$.tupled();


          public static Function1<String, Function1<Object, User>> curried()

          return User..MODULE$.curried();


          public String name()

          return this.name;

          public int age() return this.age;

          public User copy(String name, int age) return new User(name, age);

          public String copy$default$1() return name();
          public int copy$default$2() return age();

          public String productPrefix() return "User";

          public int productArity()
          return 2;

          public Object productElement(int x$1)
          int i = x$1;
          switch(i)
          default:
          throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(x$1).toString());
          case 1:
          break;
          case 0:

          return name();

          public Iterator < Object > productIterator ()
          return ScalaRunTime..MODULE$.typedProductIterator(this);

          public boolean canEqual(Object x$1)
          return x$1 instanceof User;


          public int hashCode()
          int i = -889275714; i = Statics.mix(i, Statics.anyHash(name())); i = Statics.mix(i, age()); return Statics.finalizeHash(i, 2);


          public String toString()
          return ScalaRunTime..MODULE$._toString(this);


          public boolean equals(Object x$1)
          if (this != x$1)
          Object localObject = x$1;
          int i;
          if ((localObject instanceof User)) i = 1; else i = 0;
          if (i == 0) break label96;
          User localUser = (User) x$1;
          str = localUser.name();
          String tmp42_32 = name();
          if (tmp42_32 == null)
          tmp42_32; if (str == null) break label63; tmpTernaryOp = tmp42_32; break label88;



          public User (String name, int age)
          Product
          .class.$init$(this);


          4 变量声明中的模式匹配

           val (x, y) = (10, "hello")
          println(s"x: $x, y: $y")

          val List(first, second, _*) = List(23, 15, 9, 78)
          println(s"first: $first, second: $second")

          val fir :: sec :: rest = List(23, 15 , 9, 78)
          println(s"first: $fir, second: $sec, rest: $rest")

          5 for推导式中进行模式匹配

           val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))

          // 1 原本的遍历方式
          for (elem <- list)
          println(elem._1 + " " + elem._2)


          // 2 将List的元素直接定义为元组,对变量赋值
          for ((word, count) <- list )
          println(word + ": " + count)


          // 3 可以不考虑某个位置的变量,只遍历key或者value
          for ((word, _) <- list)
          println(word)


          // 4 可以指定某个位置的值必须是多少
          for (("a", count) <- list)
          println(count)


          // 5 循环守卫
          for ((k, v) <- map if v >= 1)
          println(k + " ---> " + v)

          6 偏函数中的模式匹配

          偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如

          该偏函数的输入类型为 List[Int],而我们需要的是第一个元素是 0 的集合,这就是通过模式

          匹配实现的

          (1)偏函数定义

           val second: PartialFunction[List[Int], Option[Int]] = 
          case x :: y :: _ => Some(y)


          (2)偏函数原理

          上述代码会被 scala 编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数

          检查的函数——isDefinedAt,其返回值类型为 Boolean。

             val second = new PartialFunction[List[Int], Option[Int]] 
            //检查输入参数是否合格
            override def isDefinedAt(list: List[Int]): Boolean = list match

            case x :: y :: _ => true
            case _ => false

            //执行函数逻辑
            override def apply(list: List[Int]): Option[Int] = list match

            case x :: y :: _ => Some(y)


            (3)偏函数使用

            偏函数不能像 second(List(1,2,3))这样直接使用,因为这样会直接调用 apply 方法,而应

            该调用 applyOrElse 方法,如下

            second.applyOrElse(List(1,2,3), (_: List[Int]) => None)

            applyOrElse 方法的逻辑为 `if (ifDefinedAt(list)) apply(list) else default。如果输入参数满

            足条件,即 isDefinedAt 返回 true,则执行 apply 方法,否则执行 defalut 方法,default 方法

            为参数不满足要求的处理逻辑。

            (4)案例实操

            讲list中元组第二个元素*2

             val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))

            // 1. map转换,实现key不变,value2倍
            val newList = list.map( tuple => (tuple._1, tuple._2 * 2) )

            // 2. 用模式匹配对元组元素赋值,实现功能
            val newList2 = list.map(
            tuple =>
            tuple match
            case (word, count) => (word, count * 2)


            )

            // 3. 省略lambda表达式的写法,进行简化
            val newList3 = list.map
            case (word, count) => (word, count * 2)
               



            以上是关于Scala 模式匹配详解的主要内容,如果未能解决你的问题,请参考以下文章

            Scala 模式匹配详解

            Scala 模式匹配详解

            第26讲: Scala中的模式匹配入门实战详解

            Scala 基础(十三):Scala 模式匹配

            Scala 模式匹配

            必会Scala之模式匹配和样例类