目录
一、match
1、match
(1)基本介绍
Scala中的模式匹配类似于Java中的switch语法,但是更加强大。
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句。
(2)Java Switch
switch(expression){
case value :
//语句
break; //可选
case value :
//语句
break; //可选
//你可以有任意数量的case语句
default : //可选
//语句
}
(3)Scala的模式匹配
val oper = '#'
val n1 = 20
val n2 = 10
var res = 0
oper match {
case '+' => res = n1 + n2
case '-' => res = n1 - n2
case '*' => res = n1 * n2
case '/' => res = n1 / n2
case _ => println("oper error")
}
println("res=" + res)
(4)Scala match的细节和注意事项
-
如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句
-
如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError
-
每个case中,不用break语句,自动中断case
-
可以在match中使用其它类型,而不 仅仅是字符
-
=> 等价于 java swtich 的 :
-
=> 后面的代码块到下一个 case, 是作为一个整体执行,可以使用{} 扩起来,也可以不扩。
2、守卫
(1)基本介绍
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫
for (ch <- "+-3!") {
var sign = 0
var digit = 0
ch match {
case '+' => sign = 1
case '-' => sign = -1
// 说明..
case _ if ch.toString.equals("3") => digit = 3
case _ => sign = 2
}
println(ch + " " + sign + " " + digit)
}
3、模式中的变量
(1)基本介绍
如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量
val ch = 'V'
ch match {
case '+' => println("ok~")
case mychar => println("ok~" + mychar)
case _ => println ("ok~~")
}
4、类型匹配
(1)基本介绍
可以匹配对象的任意类型,这样做避免了使用isInstanceOf和asInstanceOf方法
val a = 6
val obj = if(a == 1) 1
else if(a == 2) "2"
else if(a == 3) BigInt(3)
else if(a == 4) Map("aa" -> 1)
else if(a == 5) Map(1 -> "aa")
else if(a == 6) Array(1, 2, 3)
else if(a == 7) Array("aa", 1)
else if(a == 8) Array("aa")
val result = obj match {
case a : Int => a
case b : Map[String, Int] => "对象是一个字符串-数字的Map集合"
case c : Map[Int, String] => "对象是一个数字-字符串的Map集合"
case d : Array[String] => "对象是一个字符串数组"
case e : Array[Int] => "对象是一个数字数组"
case f : BigInt => Int.MaxValue
case _ => "啥也不是"
}
(2)类型匹配注意事项
-
Map[String, Int] 和Map[Int, String]是两种不同的类型,其它类推。
-
在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错.
val obj = 10
val result = obj match {
case a : Int => a
case b : Map[String, Int] => "Map集合"
case _ => "啥也不是"
}
- 一个说明:
val obj = 1
val result11= obj match {
case i : Int => i
}//case i : Int => i 表示 将 i = obj (其它类推),然后再判断类型
-
如果 case _ 出现在match 中间,则表示隐藏变量名,即不使用,而不是表示默认匹配。
var obj = if(a == 1) 1
else if(a == 2) "2"
else if(a == 3) BigInt(3)
else if(a == 4) Map("aa" -> 1)
else if(a == 5) Map(1 -> "aa")
else if(a == 6) Array(1, 2, 3)
else if(a == 7) Array("aa", 1)
else if(a == 8) Array("aa")
val result1 = 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 _ => "啥也不是"
}
5、匹配数组
(1)基本介绍
-
Array(0) 匹配只有一个元素且为0的数组。
-
Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。当然可以依次类推Array(x,y,z) 匹配数组有3个元素的等等....
-
Array(0,_*) 匹配数组以0开始
for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))) {
val result = arr match {
case Array(0) => "0"
case Array(x, y) => x + "=" + y
case Array(0, _*) => "以0开头和数组"
case _ => "什么集合都不是"
}
println("result = " + result)
}
6、匹配列表
for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0))) {
val result = list match {
case 0 :: Nil => "0" //case List(0) => "0"
case x :: y :: Nil => x + " " + y //
//tail 返回一个列表,包含除了第一元素之外的其他元素,此处即匹配第一个元素为0的列表
case 0 :: tail => "0 ..."
case _ => "something else"
}
println(result)
}
7、匹配元组
for (pair <- Array((0, 1), (1, 0), (1, 1),(1,0,2))) {
val result = pair match { //
case (0, _) => "0 ..." //
case (y, 0) => y //第二个元素为0的二元祖
case _ => "other" //.
}
println(result)
}
8、对象匹配
(1)基本介绍
-
对象匹配,什么才算是匹配呢?,规则如下:
-
case中对象的unapply方法(对象提取器)返回Some集合则为匹配成功
-
返回none集合则为匹配失败
(2)案例1
object CollectionTest {
def main(args: Array[String]): Unit = {
// 模式匹配使用:
val number: Double = 36.0
number match {
case Square(n) => println(n)
case _ => println("nothing matched")
}
}
}
object Square {
def unapply(z: Double): Option[Double] = Some(math.sqrt(z))
def apply(z: Double): Double = z * z
}
案例 1注意点:
-
构建对象时apply会被调用 ,比如 val n1 = Square(5)
-
当将 Square(n) 写在 case 后时[case Square(n) => xxx],会默认调用unapply 方法(对象提取器)
-
number 会被 传递给def unapply(z: Double) 的 z 形参
-
如果返回的是Some集合,则unapply提取器返回的结果会返回给 n 这个形参
-
case中对象的unapply方法(提取器)返回some集合则为匹配成功
-
返回none集合则为匹配失败
(3)案例2
object CollectionTest {
def main(args: Array[String]): Unit = {
val namesString = "Alice,Bob,Thomas"
//说明
namesString match {
case Names(first, second, third) => {
println("the string contains three people's names")
// 打印字符串
println(s"$first $second $third")
}
case _ => println("nothing matched")
}
}
}
object Names {
def unapplySeq(str: String): Option[Seq[String]] = {
if (str.contains(",")) Some(str.split(","))
else None
}
}
案例 2 注意点:
-
当case 后面的对象提取器方法的参数为多个,则会默认调用def unapplySeq() 方法
-
如果unapplySeq返回是Some,获取其中的值,判断得到的sequence中的元素的个数是否是三个,如果是三个,则把三个元素分别取出,赋值给first,second和third
-
其它的规则不变.
二、变量声明中的模式
match中每一个case都可以单独提取出来,意思是一样的.
val (x, y) = (1, 2)
//除数和余数一起提取出来
//说明 q = BigInt(10) / 3 ,r = BigInt(10) % 3
val (q, r) = BigInt(10) /% 3
val arr = Array(1, 7, 2, 9)
val Array(first, second, _*) = arr // 提出arr的前两个元素
println(first, second)
三、for表达式中的模式
val map = Map("A"->1, "B"->0, "C"->3)
for ( (k, v) <- map ) {
println(k + " -> " + v)
}
//说明
for ((k, 0) <- map) {
println(k + " --> " + 0)
}
//说明
for ((k, v) <- map if v == 0) {
println(k + " ---> " + v)
}
四、样例类
1、基本介绍
-
样例类仍然是类
-
样例类用case关键字进行声明。
-
样例类是为模式匹配而优化的类
-
构造器中的每一个参数都成为val——除非它被显式地声明为var(不建议这样做)
-
在样例类对应的伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象
-
提供unapply方法让模式匹配可以工作
-
将自动生成toString、equals、hashCode和copy方法(有点类似模板类,直接给生成,供程序员使用)
-
除上述外,样例类和其他类完全一样。你可以添加方法和字段,扩展它们
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object NoAmount extends Amount
2、实例 1
一个类型为Amount的对象,可以用模式匹配来匹配他的类型,并将属性值绑定到变量(即:把样例类对象的属性值提取到某个变量)
object CollectionTest {
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object NoAmount extends Amount
def main(args: Array[String]): Unit = {
for (amt <- Array(Dollar(1000.0), Currency(1000.0, "RMB"), NoAmount)) {
val result = amt match {
//说明
case Dollar(v) => "$" + v
//说明
case Currency(v, u) => v + " " + u
case NoAmount => ""
}
println(amt + ": " + result)
}
}
}
3、实例 2
样例类的copy方法和带名参数
copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。
object CollectionTest {
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object NoAmount extends Amount
def main(args: Array[String]): Unit = {
val amt = Currency(29.95, "RMB")
val amt1 = amt.copy() //创建了一个新的对象,但是属性值一样
val amt2 = amt.copy(value = 19.95) //创建了一个新对象,但是修改了货币值
val amt3 = amt.copy(unit = "英镑")//创建了一个新对象,但是修改了货币单位
println(amt)
println(amt2)
println(amt3)
}
}
五、case语句的中置(缀)表达式
什么是中置表达式?1 + 2,这就是一个中置表达式。如果unapply方法产出一个元组,你可以在case语句中使用中置表示法。比如可以匹配一个List序列
object CollectionTest {
def main(args: Array[String]): Unit = {
List(1, 3, 5, 9) match { //修改并测试
//1.两个元素间::叫中置表达式,至少first,second两个匹配才行,符号::向队列的头部追加数据
//2.first 匹配第一个 second 匹配第二个, rest 匹配剩余部分(5,9)
case first :: second :: rest => println(first + second + rest.length) //
case _ => println("匹配不到...")
}
}
}
六、匹配嵌套结构
1、案例商品捆绑打折出售
现在有一些商品,请使用Scala设计相关的样例类,完成商品捆绑打折出售。要求
-
商品捆绑可以是单个商品,也可以是多个商品。
-
打折时按照折扣x元进行设计.
-
能够统计出所有捆绑商品打折后的最终价格
(1)注意点 1:将 description 绑定到第一个Book的描述
取出 val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))
这个嵌套结构中的 "漫画"
object CollectionTest {
abstract class Item // 项
case class Book(description: String, price: Double) extends Item
//Bundle 捆 , discount: Double 折扣 , item: Item* :表示可变参数,可以传无数多个item
case class Bundle(description: String, discount: Double, item: Item*) extends Item
def main(args: Array[String]): Unit = {
//给出案例表示有一捆数,单本漫画(40-10) +文学作品(两本书)(80+30-20) = 30 + 90 = 120.0
val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品",
20, Book("《阳关》", 80), Book("《围城》", 30)))
val res = sale match {
//如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有
case Bundle(_, _, Book(desc, _), _*) => desc
}
println(res)
}
}
(2)注意点 2:通过@表示法将嵌套的值绑定到变量。_*绑定剩余Item到rest
将 "漫画" 和
val sale = Bundle("书籍", 10, Book("漫画", 40),
Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))
这个嵌套结构中的 "漫画" 和 紫色的部分 绑定到变量,即赋值到变量中。
object CollectionTest {
abstract class Item // 项
case class Book(description: String, price: Double) extends Item
//Bundle 捆 , discount: Double 折扣 , item: Item* :表示可变参数,可以传无数多个item
case class Bundle(description: String, discount: Double, item: Item*) extends Item
def main(args: Array[String]): Unit = {
//给出案例表示有一捆数,单本漫画(40-10) +文学作品(两本书)(80+30-20) = 30 + 90 = 120.0
val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20,
Book("《阳关》", 80), Book("《围城》", 30)))
val result2 = sale match {
case Bundle(_, _, art @ Book(_, _), rest @ _*) => (art, rest)
}
println("result2 = " + result2)
println("art = " + result2._1)
println("rest = " + result2._2)
}
}
(3)注意点 3:不使用_*绑定剩余Item到rest
object CollectionTest {
abstract class Item // 项
case class Book(description: String, price: Double) extends Item
//Bundle 捆 , discount: Double 折扣 , item: Item* :表示可变参数,可以传无数多个item
case class Bundle(description: String, discount: Double, item: Item*) extends Item
def main(args: Array[String]): Unit = {
//给出案例表示有一捆数,单本漫画(40-10) +文学作品(两本书)(80+30-20) = 30 + 90 = 120.0
val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20,
Book("《阳关》", 80), Book("《围城》", 30)))
val result2 = sale match {
//说明因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是WrappedArray了。
case Bundle(_, _, art @ Book(_, _), rest) => (art, rest)
}
println("result2 = " + result2)
println("art =" + result2._1)
println("rest=" + result2._2)
}
}
2、求解:包含递归操作
object CollectionTest {
abstract class Item // 项
case class Book(description: String, price: Double) extends Item
//Bundle 捆 , discount: Double 折扣 , item: Item* :表示可变参数,可以传无数多个item
case class Bundle(description: String, discount: Double, item: Item*) extends Item
def main(args: Array[String]): Unit = {
//给出案例表示有一捆数,单本漫画(40-10) +文学作品(两本书)(80+30-20) = 30 + 90 = 120.0
val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20,
Book("《阳关》", 80), Book("《围城》", 30)))
val money = price(sale)
println(money)//120.0
}
def price(it: Item): Double = {
it match {
case Book(_, p) => p
//生成一个新的集合,_是将its中每个循环的元素传递到price中it中。递归操作,分析一个简单的流程
case Bundle(_, disc, its @ _*) => its.map(price _).sum - disc
}
}
}
七、密封类
如果想让case类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明为sealed,这个超类称之为密封类。
密封就是不能在其他文件中定义子类。