Scala 自学笔记 模式匹配和样例类

时间:2021-06-07 05:49:06

switch

var sign = ...
val ch: Char = ...

ch match{ // 无需像java一样 加break
case '+' => sign =1
case '-' => sign = -1
case _ => sign = 0 // case _捕获所有,如果没有模式匹配,代码会抛出Match Error
}

与if类似,match也是表达式,不是语句,上面的可以简化为
sign = ch match{
case '+' => 1
case '-' => -1
case _ => 0
}
可以在match表达式中使用任何类型
color match {
case Color:Red => ...
case Color.Black => ...
...
}

守卫

假定我们要匹配所有数字,在java的switch中,可以添加多个case,如case '0: case '1': ...case '9'
在Scala中,需要给模式添加守卫,
守卫可以是任何Boolean条件
ch match{
case '+' => ...
case '-' =>...
case _ if Character.isDigit(ch) => digit = Character.digit(ch,10)
case _ = > ...
}

模式中的变量

str(i) match{
case '+' => sign =1
case '-' => sign = -1
case ch => digit = Character.digit(ch,10)
}
可以将 case_ 看做是这个特性的一个特殊情况,只不过变量名是 _ 罢了。

可以在守卫中使用变量
str(i) match{
case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10)
...
}
变量模式与常量的冲突import scala.math._x match{  case Pi = > ... // 这里Pi 被认为是 常量}规则是,变量必须以小写字母开头如果就是有那么个常量也是小写,就必须包在反引号中import java.io.File._str match{  case `pathSeparator` => ... //  被认作常量}

类型模式

在scala中,类型匹配,应优先考虑模式匹配,而不是isInstanceOf操作符
obj match{
case x: Int => x
case s: String => Integer.parseInt(s) //值被当做 String 绑到s, 这里不需要用asInstanceOf做类型转换
case _: BigInt => Int.MaxValue
case _ => 0
}
在做类型匹配时,必须提供一个变量名,否则将会拿对象本身来进行匹配obj match{  case _: BigInt => Int.MaxValue // 匹配任何类型为 BigInt的对象  case BigInt = > -1 //匹配类型为Class的BigInt对象}匹配发生在运行期,Java虚拟机中泛型的类型信息是被擦掉的。因此不能用来匹配特定的Map类型case m:Map[String, Intn] => ... // 错误只能匹配通用的Mapcase m:Map[_,_] => ... // 正确注:数组元素的类型信息是完好的, 可以匹配Array[Int]


匹配数组、列表和元组

匹配数组
case match{
case Array(0) => "0" //只有一个0
case Array(x,y) => x+" "+y //有2个元素
case Array(0, _*) = > "0 ..." //以0开始的
case _ => "something else"
}
可以用同样的方式匹配列表,使用List表达式
或者 也可以使用 :: 操作符
lst match{
case 0 :: Nil => '0'
case x :: y :: Nil => x + " " + y
case 0 :: tail => "0 ..."
case _ => "something else"
}
元组匹配
pair math {
case (0, _) => "0 ..."
case (y, 0) => y+ " 0"
case _ => "neither is 0"
}
以上匹配,都可以让我们轻松地访问复杂结构的各组成部分,这种操作被称为“析构”


</pre><p></p><pre>
提取器

arr match{
case Array(0,x) => ...
}
通过调用Array的 unApplySeq方法,参数为 arr
Array.unapplySeq(arr),产出一个序列的值,第一个值与0 进行比较,而第二个值被赋值给x。

正则表达式做为提取器
val pattern = "([0-9]+) ([a-z]+)".r
"99 bottles" match {
case pattern(num, item) => ... // 将num设为"99" , item 设为 "bottles"
}
pattern.unapplySeq("99 bottles")产出一系列匹配分组的字符串,分别赋值给num和item
这里的提取器不是一个伴生对象,而是一个正则表达式对象

变量声明中的模式

上面展示了模式匹配中可以带变量
其实也可以在变量声明中使用这样的模式
val (x,y) = (1,2)
val (q, r) = BigInt(10) /% 3 // /%方法返回商和余数的对偶
val Array(first, second,, _*) = arr // arr的第一个和第二个元素赋值给first和second

for表达式中的模式

import scala.collection.JavaConversions.propertiesAsScalaMap
//将Java的Properties转换为Scala的Map
for( (k,v) <- System.getProperties())
println( k+ " -> "+ v)

在for推导式中,失败的匹配将被忽略
for( (k, "") <- System.getProperties()) // 只打印出所有值为空白的键,跳过所有其他键
prinltn(k)

也开始用守卫:
for( ( k, v) <- System.getProperties() if v =="")
println(k)


样例类

一种特殊的类,被优化以被用于模式匹配
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
针对单例的样例对象
case object Nothing extends Amount

当我们有个类型为Amount的对象时,可以用模式匹配来匹配到它的类型,并将属性值绑定到变量,
amt match{
case Dollar(v) => "$s" + v
case Currency(_,u) => "Oh noes, I got " + u
case Nothing => "" // 样例类的实例使用(),样例对象不适用 圆括号
}
声明样例类1、构造器中每一个参数都是val,除非显示地声明为var2、伴生对象提供apply ,比如 Dollar(29.95)3、提供unapply方法,让模式匹配可以工作4、生成toString equals hashCode copy 方法,除非显示给出这些方法的定义。除上述外,样例类和其他类完全一样,可以添加方法和字段,扩展他们 等等。


copy方法和带名参数
val amt = Currency(29.95, "EUR")val price = amy.copy() //创建一个与现有对象值相同的新对象

带名参数来修改某些属性
val price = amt.copy(value = 19.95) // Currency(19.95, "EUR"")
val price = amt.copy(unit = "CHF") // Currency(29.95, "CHF")

case语句中的中置表示法(没有这种表示法,也能work,就是写多个模式匹配嵌套的时候比较丑)

如果upapply方法产出一个对偶,则case语句中可以使用中置表示法
尤其是对于有两个参数的样例类
amt match { case a Currency u => ...} // 等同于 case Currency(a, u)
这个样例比较做作,这个特性的本意是 匹配序列。
每个List对象要么是Nil,要么是样例类:: ,定义如下
case class :: [E] (head: E, tail: List[E]) extends List[E]
因此,可以这么写:
lst match{ case h :: t => ...} // 等同于 case :: (h, t), 将调用 ::.unapply(lst)

在解析一章中,类似的用于将解析结果组合在一起的~样例类,也是用中置表达式
result match{ case p ~ q => ...} //等同于 case ~(p, q)
当把多个中置表达式放一起,更易读
result match { case p ~ q ~ r => ...} // 这个写法好过 ~(~(p,q),r)
如果操作符以冒号结尾,则是从右向左结合的
case first :: second :: rest // case ::(first, ::(second, rest))
中置表示法可用于任何返回对偶的unapply方法case object +:{  def unapply[T](input : List[T]) =     if (input.isEmpty) None else Some((input.head, input.tail))}这样一来,就可以用+:来析构列表了1 +: 7 +: 2 +: 9 +: Nil match{  case first +: second +: rest => first + second + rest.length}

匹配嵌套结构

样例类经常被用于嵌套结构
例如,某个商店售卖的物品,捆绑一起打折出售
abstract class Item
case class Article(description: String, price: Double) extends Item
case class Bundle(description: String, discount: Double, item: Item*) extends Item
因为不用new,所以很容易定义出嵌套对象定义
Boudle("Father's day special", 20.0,
Article("Scala for the Impatient", 39.95),
Bundle("Anchor Distillery Sampler", 10.0,
Article("Old Potrero Straight Rye Whisky", 79.95),
Article("Junipero Gin", 32.95)))
模式可以匹配到特定的嵌套,
case Bundle(_,_,Article(descr, _),_*) => ... //这里能匹配到 descr = "Scala for the Impatient" ,序列里的第一个Artical的第一个参数

用@表示法将嵌套的值绑到变量
case Bundle(_,_, art@Article(_,_), rest @ _*) =>...
art绑定了 Bundle中的第一个Article
rest则是剩余Item的序列, 这里的 _*是必须的
case Bundle(_,_, art @Article(_,_), rest) => ...
将只能匹配到那种只有一个Articla,再加上正好一个Item的Bundle。

作为该特性的一个实际应用,以下是一个计算某Item价格的函数:
def price (it:Item) :Double = it match{
case Articla(_,p) => p
case Bundle(_, disc, its @ _*) => its.map(price _).sum - disc // 递归调用price 计算价格
}


样例类的一些事项

1、样例类会自动生成toString equals hashCode copy方法
但是那些扩展其他样例类的样例类,则不会生成,并会得到一个编译器警告。
如果需要从多层次的继承来讲样例类的通用行为抽象到样例类外部的话,请只把几成熟的叶子部分做成样例类。

2、样例类默认都是不可变的,一个Currency(10,"eur") 和 任何其他Currency(10,"eur") 都是等效的,这也是equals 和 hashCode方法实现的依据
那些带有可变字段的样例类是不推荐对的,
对于可变类,我们应该总是从那些不可变字段计算其哈希码

密封类

在用样例类来做模式匹配时,让编译器帮忙确保已经列出了所有可能的选择。
要达到这个目的,需要将样例类的通用超类声明为sealed
sealed abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
密封类的所有子类必须在与该密封类相同的文件中定义。
如果某个类是密封的,那么在编译器所有子类就是可知的,因为编译器可以检查模式语句的完整性。
让所有(同一组)样例类都扩展某个密封的类或特质是个好的做法。

模拟枚举样例类让我们可以在Scala中模拟出枚举类型sealed abstract class TrafficLightColor //超类声明sealed, 让编译器可以帮我们检查match语句是否完整case object Red extends TrafficLightColorcase object Yellow extends TrafficLightColorcase object Green extends TrafficLightColorcolor match{  case Red => "stop"  case Yellow => "hurrp up"  case Green => "go"}如果觉得实现方式有些过重,可以用Enumeration助手类

Option类型

Option类型用样例类来表示那种可能存在、也可能不存在的值
样例子类Some包装了某个值,Some("Fred')
样例对象Option表示没有值
Option支持泛型,Some("Fred")的类型为Option[String]
Map类的get方法返回一个Option,有值将该值包在Some中返回,无值返回None
scores.get("Clat") match{
case Some(score) => println(score)
case None => println("No score")
}
也可以简单点
val clatScore= scores.get("clat")
if(clatScore.isEmpty) println("No score")
else println(clatScore.get)
再简单点
println(clatscore.getOrElse("No score")) // Option的getOrElse方法
由于十分普遍,所以Map也提供了getOrElse
println(scores.getOrElse("clat","No score"))
或者可以用for推导,略过None值
for(score <- scroes.get("clat")) println(score) // 如果None,什么都不会发生,如果返回Some,则score将被绑定到它的内容
或者也可以将Option当做一个要么空,要么带单个元素的集合,使用map foreach filter 方法
scores.get("Clat").foreach(println _) //如果None,什么也不做


偏函数

被包在花括号内的一组case语句是一个偏函数
并非对所有输入值都有定义
它是PartialFunction[A,B]类的一个实例,A是参数类型,B是返回类型
apply方法从匹配到的模式计算函数值
isDefinedAt方法在输入至少匹配其中一个模式时回true
val f: PartialFunction[Char, Int] = {case '+'=>1; case '-' => -1}
f('-') // 调用f.apply('-'),返回 -1
f.isDefinedAt('0') //false
f('0') // 抛出MatchError
有一些方法接受PartialFunctioin作为参数
如Gentraversable特质的collect方法,将一个偏函数应用到所有在该偏函数有定义的元素,返回包含这些结果的序列
"-3+4".collect{ case '+' => 1; case '-' => -1} //Vector(-1,1)
注:偏函数表达式必须位于编译器可以推断出返回类型的上下文中
1、把它赋值给一个带有类型声明的变量(上面的f变量)
2、将它作为参数传递