Scala 模式匹配详解

Scala 模式匹配详解,第1张

Scala 模式匹配详解

大数据技术AI

Flink/Spark/Hadoop/数仓,数据分析、面试,源码解读等干货学习资料

101篇原创内容

公众号

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)
  • 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如:apply、unapply、toString、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
   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> 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="060105....")
 public class User
   implements Product, Serializable
{
   private final String name;
   private final int age;

   public static Option> 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, User> tupled()
   {
     return User..MODULE$.tupled();
   }

   public static Function1> 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)
   }

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5688375.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存