一、match基础

1、基本介绍

1. Scala中的模式匹配类似于Java中的switch语法,但是比Java更加强大;
2. 模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个
case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。
如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句,如果所有case都不匹配,而且
也没有case_分支,会抛出异常(MatchError)。

match入门示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest01 {

    def main(args: Array[String]): Unit = {

        val list_operate = List("+", "-", "*", "/", "^")
        val num01 = 19
        val num02 = 7
        var operate_res = 0

        /**
          * 说明:match
          *     1. match(类似Java switch)和case是关键字;
          *     2. 如果匹配成功,则执行“=>”后面的代码块;
          *     3. 匹配的顺序从上到下,匹配到一个就执行对应的代码,执行完以后就退出;
          *     4. “=>”后面的代码不许要写break,会自动退出match;
          *     5. 如果一个都没有匹配到,则会执行case_后面的代码块。
          *
          */

        for (ele <- list_operate) {
            ele match {
                case "+" => operate_res = num01 + num02
                case "-" => operate_res = num01 - num02
                case "*" => operate_res = num01 * num02
                case "/" => operate_res = num01 / num02
                case _ => println("无此操作符:" + ele)
            }
            println(s"根据操作符“${ele}”运算的结果是:" + operate_res)
            operate_res = 0
        }

    }

}
===========================运行结果=================================
根据操作符“+”运算的结果是:26
根据操作符“-”运算的结果是:12
根据操作符“*”运算的结果是:133
根据操作符“/”运算的结果是:2
无此操作符:^
根据操作符“^”运算的结果是:0
===========================运行结果=================================

match的细节和注意事项

1. 如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句;
2. 如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError;
3. 每个case中,不用break语句,自动中断case;
4. 可以在match中使用其它类型,而不仅仅是字符;
5. => 等价于java swtich的":" ;
6. => 后面的代码块到下一个 case, 是作为一个整体执行,可以使用{} 扩起来,也可以不扩。

二、match守卫

基本介绍

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

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest02 {

    def main(args: Array[String]): Unit = {

        val map01 = Map("Tom" -> 23, "Jack" -> 15, "Leo" -> 39, "Tony" -> 55, "Alic" -> 85)

        // 遍历
        for ((name, age) <- map01) {

            /**
              * 说明:模式匹配之守卫
              *     1. 如果case后面有条件守卫,即if语句,那么这是的“_”不是表示默认匹配!!!
              */

            age match {
                case _ if (age > 0 && age < 18) => println(s"${name}今年${age}岁,未成年!!!")
                case _ if (age >= 18 && age < 25) => println(s"${name}今年${age}岁,成年人!!!")
                case _ if (age >= 25 && age < 35) => println(s"${name}今年${age}岁,青壮年!!!")
                case _ if (age >= 35 && age < 60) => println(s"${name}今年${age}岁,中老年!!!")
                case _ if (age >= 60 && age < 80) => println(s"${name}今年${age}岁,老年人!!!")
                case _ => println(s"${name}今年${age}岁,吉祥老年人!!!")
            }
        }

    }

}
===========================运行结果=================================
Tom今年23岁,成年人!!!
Leo今年39岁,中老年!!!
Tony今年55岁,中老年!!!
Alic今年85岁,吉祥老年人!!!
Jack今年15岁,未成年!!!
===========================运行结果=================================

三、模式中的变量

基本介绍

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

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest03 {

    def main(args: Array[String]): Unit = {

        val name_infos = Map("Jack" -> true, "Tom" -> false, "Leo" -> false)

        for ((name, boolean) <- name_infos) {

            // 如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量.
            boolean match {
                // 此时会将boolean变量的值赋值给authorization变量
                case authorization => getInfo(authorization, name)
                case _ => println(s"${name}是火星来的吧,无此人任何蛛丝马迹!")
            }
        }

        /**
          * match是一个表达式,因此可以有返回值;
          * 返回值就是匹配到的代码块最后一句话的值。
          */

        val name = "jack"
        val name_res = name match {
            case "jack" => name + " hello !!!"
            case _ => println("ok !!!")
        }
        println("name_res:" + name_res)

    }

    def getInfo(boolean: Boolean, name: String): Unit = {

        if (boolean) {
            println(s"${name}有权限查看个人信息!")
        } else {
            println(s"${name}无权限查看其他人信息!")
        }

    }

}
===========================运行结果=================================
Jack有权限查看个人信息!
Tom无权限查看其他人信息!
Leo无权限查看其他人信息!
name_res:jack hello !!!
===========================运行结果=================================

四、类型匹配

基本介绍

可以匹配对象的任意类型,这样做避免了使用isInstanceOf和asInstanceOf方法。

类型匹配注意事项

1. Map[String, Int] 和Map[Int, String]是两种不同的类型,其它类推;
2. 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错;
	例如:
		val obj = 10
		val result = obj match {
			 case a : Int => a
			 // case b : Map[String, Int] => "Map集合"    // 这行编译器就会直接报错!!!
			 case _ => "啥也不是"
		}
3. 一个说明:
		
		val result = obj match {
			 case i : Int => i
		} 
		
  case i : Int => i 表示 将 i = obj (其它类推),然后再判断类型

4. 如果 case _ 出现在match 中间,则表示隐藏变量名,即不使用,而不是表示默认匹配。
		var obj: Any = 0
        val result = obj match {
            case a : Int => a
            case _ : BigInt => Int.MaxValue //看这里!
            case b : Map[String, Int] => "对象是一个字符串-数字的Map集合"
            case c : Map[Int, String] => "对象是一个数字-字符串的Map集合"
            case d : Array[String] => "对象是一个字符串数组"
            case e : Array[Int] => "对象是一个数字数组"
            case _ => "啥也不是"
        }

五、匹配数组

基本介绍

1. Array(0) 匹配只有一个元素且为0的数组;
2. Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。当然可以依次类推Array(x,y,z)匹配数组
有3个元素的等等....;
3. Array(0,_*) 匹配数组以0开始

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest04 {

    def main(args: Array[String]): Unit = {

        val arrs = Array(Array(0), Array(1, 0), Array(0, 1, 0),Array(1, 1, 0), Array(1, 1, 0, 1))

        for (arr <- arrs) {
            val result = arr match {
                case Array(0) => "0"  // 匹配数组只有一个元素,且为0
                case Array(x, y) => x + "=" + y  // 匹配数组只有两个元素,且元素的值赋值给x和y
                case Array(0, _*) => "以0开头和数组"  // 匹配以0开头的数组
                case _ => "不是需要匹配的需求!"
            }
            println("result:" + result)
        }

        /*
        练习:把数组Array(10, 30)两个元素的位置互换,即Array(30, 10)
         */

        val arr01 = Array(10, 30)
        val res01 = arr01 match {
            case Array(x, y) => Array(y, x)
        }
        printf("res01:(%s, %s)", res01(0), res01(1))
    }

}
===========================运行结果=================================
result:0
result:1=0
result:以0开头和数组
result:不是需要匹配的需求!
result:不是需要匹配的需求!
res01:(30, 10)
===========================运行结果=================================

六、匹配列表

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest05 {

    def main(args: Array[String]): Unit = {

        val arr_list = Array(List(0, 1, 0), List(1, 0), List(0, 0, 0), List(1, 0, 0), List(1), List(0, 0))

        for (list <- arr_list) {
            val result = list match {
                case 1 :: Nil => "1"   // 以1开头的列表,且只有1这个元素
                case x :: y :: Nil => x + " - " + y   // 含有两个元素的列表
                case 0 :: tail => "0 ..." // 以0开头的列表,后面元素任意。
                case _ => "其他默认的!"
            }
            println("result:" + result + " ---> " + list)
        }

		/*
            练习:如果要匹配 List(88) 这样的只含有一个元素的列表,并原值返回.
         */
        val list01 = List(88)
        val list02 = list01 match {
            case x :: Nil => x :: Nil  // 直接返回
        }
        println("list02:" + list02)
        
    }

}
===========================运行结果=================================
result:0 ... ---> List(0, 1, 0)
result:1 - 0 ---> List(1, 0)
result:0 ... ---> List(0, 0, 0)
result:其他默认的! ---> List(1, 0, 0)
result:1 ---> List(1)
result:0 - 0 ---> List(0, 0)
list02:List(88)
===========================运行结果=================================

七、匹配元组

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest06 {

    def main(args: Array[String]): Unit = {

        val arr_tuple = Array((0, 1), (1, 0), (1, 1),(1,0,2), (0, 1, 2, 3), (0), (1, 3, 4, 0))
        for (pair <- arr_tuple) {
            val result = pair match {
                case (0, _) => "0 ..."   // 以0开头,两个元素的tuple
                case (y, 0) => y    // 以0结尾,两个元素的tuple
                case _ => "other"
            }
            println(result + " --> " + pair)
        }

    }

}
===========================运行结果=================================
0 ... --> (0,1)
1 --> (1,0)
other --> (1,1)
other --> (1,0,2)
other --> (0,1,2,3)
other --> 0
other --> (1,3,4,0)
===========================运行结果=================================

八、对象匹配

基本介绍

对象匹配,什么才算是匹配呢?,规则如下:
	1. case中对象的unapply方法(对象提取器)返回Some集合则为匹配成功;
	2. 返回none集合则为匹配失败。

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest07 {

    def main(args: Array[String]): Unit = {

        val res = Accumulation(7.0)  // 49
        res match {

            /**
              * 说明:case Accumulation(num)的运行机制
              *     1. 当匹配到case Accumulation(num);
              *     2. 调用Accumulation的unapply(num: Double),num的值就是res
              *     3. 如果对象提取器unapply(num: Double)返回的是Some(5),则表示匹配成功,同时将5赋值给Accumulation(num)的num;
              *     4. 如果对象提取器unapply(num: Double)返回的是None,则表示匹配不成功!
              */

            case Accumulation(num) => println("匹配成功,num:" + num)
            case _ => println("什么也没有匹配到!")
        }

        // 练习
        val names = "Alice,Bob,Thomas"
        names match {

            /**
              * 当执行case NameStr(first, second, third)时:
              * 1. 会调用unapplySeq(str: String),把names的值传给str
              * 2. 如果返回的是Some("Alice","Bob","Thomas"),会分别赋值给(first, second, third)
              *     这里需要注意:返回的值个数需要和(first, second, third)个数一样!!!
              * 3. 如果返回的None,表示匹配失败。
              */

            case NameStr(first, second, third) => {
                println("字符串三个人名分别是:" + first + "," + second + "," + third)
            }
        }

    }

}

object Accumulation {

    /**
      * 说明:对象提取器
      *     1. unapply方法是对象提取器;
      *     2. 接收的类型:num: Double;
      *     3.返回的类型:Option[Double]
      *     4. 返回的值是Some(math.sqrt(num)),即返回num的开平方的值,并放入到Some(x)
      */

    def unapply(num: Double): Option[Double] = {
        println("对象提取器unapply被调用,num值:" + num)
        Some(math.sqrt(num))
        // None  // 或者返回None值
    }

    def apply(num: Double): Double = {
        num * num
    }

}

object NameStr {

    // 当构造器是多个参数时,就会触发这个对象提取器
    // (这个unappleSeq()方法是一个序列的提取器!!!,返回的也是一个序列的Option)
    def unapplySeq(str: String): Option[Seq[String]] = {

        if (str.contains(",")) {
            Some(str.split(","))
        } else {
            None
        }

    }

}
===========================运行结果=================================
对象提取器unapply被调用,num值:49.0
匹配成功,num:7.0
字符串三个人名分别是:Alice,Bob,Thomas
===========================运行结果=================================

九、变量声明中的模式

基本介绍

match中每一个case都可以单独提取出来,意思是一样的。

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest08 {

    def main(args: Array[String]): Unit = {

        val (x, y) = (1, 2)
        println("x:" + x)  // 1

        val (q, r) = BigInt(10) /% 3  //说明  q = BigInt(10) / 3 r = BigInt(10) % 3
        println("q:" + q)  // 3
        println("r:" + r)  // 1

        val arr = Array(1, 7, 2, 9)
        val Array(first, second, _*) = arr // 提出arr的前两个元素
        println(first, second)  // (1,7)
        
    }

}

十、for表达式中的模式

基本介绍

for循环也可以进行模式匹配。

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest09 {

    def main(args: Array[String]): Unit = {

        val map = Map("Leo" -> 19, "Jack" -> 25, "Tom" -> 32)
        // 遍历所有key-value
        for ((k, v) <- map) {
            println(k + " --> " + v)
        }

        println("---------------华丽分隔符------------------")

        // 只遍历value = 25的key-value,其它的过滤掉
        for ((k, 25) <- map) {
            println(k + " --> " + 25)
        }

        println("---------------华丽分隔符------------------")

        // 上面的另外一种写法
        for ((k, v) <- map if(v == 25)) {
            println(k + " --> " + v)
        }

    }

}
===========================运行结果=================================
Leo --> 19
Jack --> 25
Tom --> 32
---------------华丽分隔符------------------
Jack --> 25
---------------华丽分隔符------------------
Jack --> 25
===========================运行结果=================================

十一、样例(模板)类

快速入门案例

	abstract class Amount
	case class Dollar(value: Double) extends Amount 
	case class Currency(value: Double, unit: String) extends Amount
	case object NoAmount extends Amount
	
	这里的 Dollar,Currencry, NoAmount  是样例类。

基本介绍

1. 样例类仍然是类;
2. 样例类用case关键字进行声明;
3. 样例类是为模式匹配而优化的类;
4. 构造器中的每一个参数都成为val —> 除非它被显式地声明为var(不建议这样做);
5. 在样例类对应的伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象;
6. 提供unapply方法让模式匹配可以工作;
7. 将自动生成toString、equals、hashCode和copy方法(有点类似模板类,直接给生成,供程序员使用);
8. 除上述外,样例类和其他类完全一样。你可以添加方法和字段,扩展它们

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest10 {

    def main(args: Array[String]): Unit = {

        // 实践一:体验使用样例类方式进行对象匹配的简洁性!
        // 当我们有一个类型为Amount的对象时,可以用模式匹配来匹配他的类型,
        // 并将属性值绑定到变量(即:把样例类对象的属性值提取到某个变量,使该功能有用!)
        val amount_arr = Array(Dollar(10000.0), Currency(5000.0, "RMB"), NoAmount, "AAA")

        for (ele <- amount_arr) {
            ele match {
                case Dollar(x) => println("匹配Dollar样例类:" + x)
                case Currency(x, y) => println("匹配Currency样例类:" + x + y)
                case NoAmount => println("匹配NoAmount样例类!")
                case _ => println("什么都没有匹配到" + ele + "!")
            }
        }

        /**
          * 实践二:
          *     1. 样例类的copy方法和带名参数;
          *     2. copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。
          */

        val amt = new Currency(2999.89, "RMB")  // 创建一个对象
        val amt_copy = amt.copy()  // 克隆一个对象amt_copy,此对象和amt属性一样
        val amt_value = amt.copy(value = 2000)  // 克隆一个对象amt_value,并修改了金额
        val amt_unit = amt.copy(unit = "$")  // 克隆一个对象amt_unit,并修改了单位
        
    }

}

abstract class Amount
case class Dollar(value: Double) extends Amount {  // 样例类
    println("Dollar输出:" + value)
}

case class Currency(value: Double, unit: String) extends Amount {  // 样例类
    println("Currency输出:" + value + unit)
}

case object NoAmount extends Amount {  // 样例类
    println("NoAmount无输出...")
}
===========================运行结果=================================
Dollar输出:10000.0
Currency输出:5000.0RMB
NoAmount无输出...
匹配Dollar样例类:10000.0
匹配Currency样例类:5000.0RMB
匹配NoAmount样例类!
什么都没有匹配到AAA!
Currency输出:2999.89RMB
Currency输出:2999.89RMB
Currency输出:2000.0RMB
Currency输出:2999.89$
===========================运行结果=================================

十二、case语句的中置(缀)表达式

基本介绍

	什么是中置表达式?1 + 2,这就是一个中置表达式。如果unapply方法产出一个元组,你可以在case语句中
使用中置表示法。比如可以匹配一个List序列。

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest11 {

    def main(args: Array[String]): Unit = {

        val  list01 = List(1, 3, 5, 12, 15)
        list01 match {
            // 1. 两个元素之间“::”叫中置表达式,至少first,second,third三个匹配才行。
            // 2. first匹配第一个,second匹配第二个,third匹配第三个,rest匹配剩余部分。
            case first :: second :: third :: rest => {
                println("匹配结果:" + first + " -> " + second + " -> " + third + " -> " + rest)
            }
            case _ => println("匹配不到...")
        }

    }

}
===========================运行结果=================================
匹配结果:1 -> 3 -> 5 -> List(12, 15)
===========================运行结果=================================

十三、匹配嵌套结构

基本介绍

操作原理类似于正则表达式。

示例代码:

package com.lj.scala.matchoperate

/**
  * @author Administrator
  * @create 2020-03-17
  */
object MatchTest12 {

    def main(args: Array[String]): Unit = {

        /*
            实践 -- 商品捆绑打折出售:
                现在有一些商品,请使用Scala设计相关的样例类,完成商品可以捆绑打折出售。要求
                    1. 商品捆绑可以是单个商品,也可以是多个商品。
                    2. 打折时按照折扣xx元进行设计。
                    3. 能够统计出所有捆绑商品打折后的最终价格。
         */

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

        val res01  =sale match {

            // 使用case语句得到“漫画”
            // 说明:如果进行对象匹配时,不想接受某些信息,则使用“_”忽略即可,“_*”表示忽略所有
            case Bundle(_, _, Book(desc, _), _*) => {
                desc
            }
        }
        println("res01:" + res01)

        val res02  =sale match {
            // 通过“@”表示法将嵌套的值绑定到变量【art @ Book(_, _)会将Book对象赋值给art】。
            // “_*”绑定剩余Iteam到rest
            case Bundle(_, _, art @ Book(_, _), rest @ _*) => {
                (art, rest)
            }
        }
        println("res02:" + res02)

        val res03  =sale match {
            // 不使用“_*”绑定剩余Iteam到rest
            // 由于[rest @ _*]会有WrappedArray出现的,因为“_*”表示所有,系统不知道“_*”有多少个
            // Bundle,所以会有WrappedArray。如果明确知道就一个Bundle,可以不用写“_*”
            case Bundle(_, _, art @ Book(_, _), rest) => {
                (art, rest)
            }
        }
        println("res03:" + res03)

		/**
          *  分析执行过程:
          *  val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))
          * 1. 执行price(sale)方法
          * 2. 根据传入的sale变量,match匹配到“case Bundle(_, discount, its @ _*)”,得到的变量
          * 3. discount = 10,its = “10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30))”
          * 4. 后面继续执行代码:its.map(price).sum - discount继续调用price函数,得到p = 40
          * 运算40 - 10 = 30
          * 5. 继续递归调用price函数,得到discount = 20,接着 p = 30, p = 80
          * 6. 30 + 30 + 80 - 20 = 120
          */
        println("price:" + price(sale))

    }

    def price(it: Item): Double = {
        it match {
            case Book(_, p) => p
            // 生成一个新的集合,_是将its中每个循环的元素传递到price中it中。递归操作!
            case Bundle(_, discount, its @ _*) =>its.map(price).sum - discount
        }
    }

}

// 设计样例类
abstract class Item

case class Book(description: String, price: Double) extends Item {

}

case class Food(description: String, price: Double) extends Item {

}

case class Bundle(description: String, discount: Double, item: Item*) extends Item {

}
===========================运行结果=================================
res01:漫画
res02:(Book(漫画,40.0),WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))))
res03:(Book(漫画,40.0),Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))
price:120.0
===========================运行结果=================================

十四、密封类

基本介绍

	如果想让case类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明
为sealed,这个超类称之为密封类。
	密封就是不能在其他文件中定义子类。

对以前的知识回顾,加深基础知识!
学习来自:北京尚硅谷韩顺平老师—尚硅谷大数据技术之Scala
每天进步一点点,也许某一天你也会变得那么渺小!!!


版权声明:本文为XuanAlex原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/XuanAlex/article/details/104918977