一、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 版权协议,转载请附上原文出处链接和本声明。