- Scala 模式匹配支持獲取對(duì)象狀態(tài);獲取對(duì)象狀態(tài)的操作往往稱為“提取”或“解構(gòu)”。[P86]
match 中的值、變量和類型
可以使用一個(gè)
Any類的變量放到最后來充當(dāng)default。[P88]編譯器會(huì)自動(dòng)推斷所有
case子句返回值類型的最近公共父類型。[P88]在被匹配或提取的值中,編譯器假定以大寫字母開頭的為類型名,以小寫字母開頭的為變量名。[P89]
-
注意一下模式匹配的變量含義與作用域:[P89]
def checkY(y: Int) = { for { x <- Seq(1, 2, 3) } { val str = x match { case y => "found y!" // 錯(cuò)誤:并不是與變量 y 的值匹配,而是聲明了一個(gè) Any 類型的 y,這樣會(huì)收到系統(tǒng)警告。 case i: Int => "int: " + i } println(str) } } checkY(1)使用
``包圍變量以引用已經(jīng)定義的變量。def checkY(y: Int) = { for { x <- Seq(1, 2, 3) } { val str = x match { case `y` => "found y!" // 正確 case i: Int => "int: " + i } println(str) } } checkY(1) 邏輯或語法:
case _: Int | _: Double => ...
序列的匹配
-
序列的基礎(chǔ)語法 [P91]
- 使用
.empty[A]構(gòu)造空序列。 - 任意類型的空序列 用
Nil表示。
- 使用
-
關(guān)于
Seq的特殊語法:[P91 - 92]def seqToString[T](seq: Seq[T]): String = seq match { case head +: tail => s"$head +: " + seqToString(tail) // 1 case Nil => "Nil" }-
+:是“構(gòu)造” 操作符,以:結(jié)尾的方法向右結(jié)合,即向Seq的尾部結(jié)合。head和tail是Seq自帶的兩個(gè)方法,但在這里,按照慣例被解釋為一個(gè)變量,作用是提取一個(gè)非空序列的頭部(第一個(gè)元素)和尾部(除了第一個(gè)元素外的其他元素)。
-
Map不是Seq的子類型,要通過map.toSeq生成seq。[P92]
case 中的 guard 語句 [P95]
for ( i <- Seq(1, 2, 3, 4)) {
i match {
case _ if i % 2 == 0 => println(s"even: $i")
case _ => println(s"odd: $i")
}
}
case 類的匹配
-
使用
Seq.zipWithIndex方法將一個(gè)Seq連同序號(hào)一起打印出來:[P95 -96]val itemsCosts = Seq(("Pencil", 0.52), ("Paper", 1.35)) val itemsCostsIndices = itemsCosts.zipWithIndex for (itemCostIndex <- itemsCostsIndices) { itemCostIndex match { case ((item, cost), index) => println(s"$index: $item costs $cost each") } }調(diào)用
zipWithIndex返回的元組形式為((name, cost), index)。
unapply 方法
unapply方法用于提取和解構(gòu)。當(dāng)在match中對(duì) case 類使用諸如case Person("Alice", 25, Address(_, "Chicago", _)) => ...之類的語法時(shí),會(huì)調(diào)用其unapply方法。(當(dāng)然在本例中Address也需要unapply方法)[P96]-
unapply方法的一種定義:[P96 - 97]def unapply(P: Person): Option[Tuple3[String, Int, Address]] = Some((p.name, p.age, p.address))用
Option的原因是,unapply方法可以選擇“否決”這個(gè)請(qǐng)求,返回None詳見unapplySeq。-
從 Scala 2.11.1 開始,
unapply方法可以返回任意類型?,只要該類型具有以下方法:def isEmpty: Boolean def get: T
有必要時(shí),
unapply會(huì)被遞歸調(diào)用(比如本例中的Address)。[P97]-
元組字面量語法:[P97 -> P50]
val t1 = Option[Tuple3[String, Int, Address]] = ... val t2 = Option[(String, Int, Address)] = ... val t3 = Option[ (String, Int, Address) ] = ... // 更容易閱讀 -
讓
unapply支持任意非空集合:使用:+[P97 - 98]-
:+是一個(gè)單例對(duì)象,它的unapply使用以下語法:def unapply[T, Coll](collection: Coll): Option[(T, Coll)]但是這樣的
unapply使用的調(diào)用方法為:case +: (head, tail) => ...
可以寫成如下形式:def processSeq2[T](l: Seq[T]): Unit = l match { case +: (head, tail) => println("%s +: ", head) processSeq2(tail) case Nil => println("Nil") }當(dāng)然,也可以使用
head +: tail,這是編譯器提供的語法糖。同樣的語法糖還有:case class With[?A, B](a: A, b: B) val with1: With[Stirng, Int] = With("Foo", 1) val with2: With[String, Int] = With("Bar", 2) Seq(with1, with2) foreach { w match { case s With i => println(s"$s with $i) case _ => println(s"Unknown $w") } }但是,同樣的語法不能用于初始化。
-
使用
:+逆序處理一個(gè)序列。[P99]def reverseSeqToString[T](l: Seq[T]): String = l match { case prefix :+ end => reverseSeqToString(prefix) + s" :+ $end" case Nil => "Nil" }
-
補(bǔ)充:對(duì)于
List,:+/+:需要 O(n) 的時(shí)間復(fù)雜度,對(duì)于Vector之類的其他某些序列,只需要 O(1) 的時(shí)間復(fù)雜度。[P99]
unapplySeq 方法
-
除了
apply方法外,Seq的伴隨對(duì)象還實(shí)現(xiàn)了unapplySeq方法:[P100]def apply[A](elems: A*): Seq[A] def unapplySeq[A](x: Seq[A]): Some[Seq[A]]在
case中,使用如下語法調(diào)用unapplySeq:def windows[T](seq: Seq[T]): String = seq match { case Seq(head1, head2, _*) => s"($head1, $head2), " +????? windows(seq.tail) case ... ... } -
當(dāng)然也可以使用
+:語法:[P101]def windows2[T](seq: Seq[T]): String = seq match { case head1 +: head2 +: tail => s"($head1, $head2), " +????? windows2(seq.tail) case ... ... } -
Seq的sliding方法:[P101]- 返回一個(gè)“惰性”迭代器。對(duì)這個(gè)迭代器調(diào)用
toSeq方法,可以將迭代器轉(zhuǎn)為一個(gè)collection.immutable.Stream(一個(gè)惰性列表,創(chuàng)建時(shí)即對(duì)列表的頭部元素求值,但只在需要的時(shí)候才會(huì)對(duì)列表的尾部元素求值。toList會(huì)在創(chuàng)建時(shí)對(duì)所有元素求值)。
- 返回一個(gè)“惰性”迭代器。對(duì)這個(gè)迭代器調(diào)用
可變參數(shù)列表的匹配
-
使用
name @ _*匹配可變參數(shù):[P102]case WhereIn(col, val1, vals @ _*) => ...
正則表達(dá)式匹配
-
使用
.r方法生成正則表達(dá)式。[P103]val BookExtractorRE = """Book: title=([^,]+),\s+author=(.+)""".r在
match中使用case BookExtractorRE(title, author) => ... 使用三重引號(hào)表示正則表達(dá)式字符串的原因是可以不用對(duì)正則中的
\等符號(hào)單獨(dú)進(jìn)行轉(zhuǎn)義。[P103]在三重引號(hào)內(nèi)的正則表達(dá)式中使用變量插值是無效的,如果使用了變量插值,就需要對(duì)
\等符號(hào)進(jìn)行轉(zhuǎn)義操作。[P103]
再談 case 語句的變量綁定
-
name @ object語法:[P104]person match { case p @ Person("Alice", 25, address) => ... case p @ Person("Bob", 29, a @ Addres(street, city, country)) => ... }p @ ...的語法將整個(gè)Person類的實(shí)例賦值給了變量p。如果不需要從Person實(shí)例中提取屬性值,只要寫為p: Person => ...就可以了。
再談?lì)愋推ヅ?/h2>
-
JVM 類型擦除:為了避免與舊版本代碼斷代,JVM 的字節(jié)碼不會(huì)記住一個(gè)泛型實(shí)例(如 List )中實(shí)際傳入的類型與參數(shù)信息。所以在 match 中,不能區(qū)分 Seq[String] 與 Seq[Double] ,要自定義匹配函數(shù):
x match {
case seq: Seq[_] => (s"seq ${doSeqMatch(seq)}", seq)
case _ => ("Unknown!", x)
}
def doSeqMatch[T](seq: Seq[T]): String = seq match {
case Nil => "Noting"
case head +: _ => head match {
case _: Double => "Double"
case _: String => "String"
case _ => "Unmatched seq element"
}
}
封閉繼承層級(jí)與全覆蓋匹配
如果類型的繼承層級(jí)可能發(fā)生變化,就應(yīng)當(dāng)避免使用 sealed 。[P107]
-
在父類型中,不帶參數(shù)的抽象方法可以再子類中用 val 變量實(shí)現(xiàn)。推薦的做法是:在抽象父類型中聲明一個(gè)不帶參數(shù)的抽象方法,這樣就給子類型如何具體實(shí)現(xiàn)該方法留下了巨大的自由,既可以用方法實(shí)現(xiàn),又可以用 val 變量實(shí)現(xiàn)。[P107]
sealed abstract class HttpMethod() {
def body: String
def bodyLength: body.length
}
編譯器無法判斷 Enumeration 相應(yīng)的 match 語句是否全覆蓋。[P107]
模式匹配的其他用法
-
定義變量:
val Person(name, age, Address(_, state, _)) = Person("Dean", 29, Address("1 Scala Way", "CA", "USA))
// 得到 name, age, state;
val head +: tail = List(1, 2, 3)
// head: Int = 1
// tail: List[Int] = List(2, 3)
val Seq(a, b, c) = List(1, 2, 3)
// 得到 a, b, c
val Seq(a, b, c) = List(1, 2, 3, 4) // MatchError
-
在 if 中也可以使用模式匹配,但不能用 _ 占位符。[P108]
val p = Person("Dean", 29, Address("1 Scala Way", "CA", "USA"))
if (p == Person("Dean", 29, Address("1 Scala Way", "CA", "USA"))) "yes" else "no" // "yes"
Scala 對(duì)一些非字母數(shù)字的字符做了”字符映射“,使得他們符合 JVM 規(guī)范。比如: = 會(huì)被映射為 $eq 。[?P108]
-
元組:[P109]
def sum_count(ints: Seq[Int]) = (ints.sum, ints.size)
val (sum, count) = sum_count(List(1, 2, 3, 4, 5))
// sum: Int = 15
// count: Int = 5
-
在帶復(fù)雜參數(shù)的函數(shù)字面量中使用:[P109 - 110]
case class Address(street: String, city: String, country: String)
case class Person(name: String, age: Int)
val as = Seq(
Address("1 Scala Lane", "Anytown", "USA"),
Address("2 Clojure Lane", "Othertown", "USA"))
val ps = Seq(
Person("Buck Trends", 29)
Person("Clo Jure", 28)
)
val pas = ps zip as // Seq[(Person, Address)]
pas map {
case (Person(name, age), Address(street, city, country)) =>
s"$name (age: $age) lives at $street, $city, in $country"
}
-
在正則表達(dá)式中使用模式匹配去解構(gòu)字符串:[P110]
val cols = """\*|[\w, ]+"""
val table = """\w+"""
val tail = """.*"""
val selectRE = s"""SELECT\\s*(DISTINCT)?\\s+($cols)\\s*FROM\\s+($table)\\s*($tail)?;""".r
val selectRE(distinct1, cols1, table1, otherClauses) = "SELECT DISTINCT * FROM atable;"
/*
distinct1: String = DISTINCT
cols1: String = *
table1: String = atable
otherClauses: String = ""
*/
val selectRE(distinct2, cols2, table2, otherClauses) = "SELECT col1, col2 FROM atable;"
/*
distinct1: String = null
cols1: String = "col1, col2"
table1: String = atable
otherClauses: String = ""
*/
由于使用了變量插值,在正則表達(dá)式字符串中必須增加 \ 轉(zhuǎn)義。
要謹(jǐn)慎對(duì)待默認(rèn) case 子句:什么情況下才應(yīng)該出現(xiàn)“以上均不匹配”。[P111]
JVM 類型擦除:為了避免與舊版本代碼斷代,JVM 的字節(jié)碼不會(huì)記住一個(gè)泛型實(shí)例(如 List )中實(shí)際傳入的類型與參數(shù)信息。所以在 match 中,不能區(qū)分 Seq[String] 與 Seq[Double] ,要自定義匹配函數(shù):
x match {
case seq: Seq[_] => (s"seq ${doSeqMatch(seq)}", seq)
case _ => ("Unknown!", x)
}
def doSeqMatch[T](seq: Seq[T]): String = seq match {
case Nil => "Noting"
case head +: _ => head match {
case _: Double => "Double"
case _: String => "String"
case _ => "Unmatched seq element"
}
}
如果類型的繼承層級(jí)可能發(fā)生變化,就應(yīng)當(dāng)避免使用 sealed 。[P107]
在父類型中,不帶參數(shù)的抽象方法可以再子類中用 val 變量實(shí)現(xiàn)。推薦的做法是:在抽象父類型中聲明一個(gè)不帶參數(shù)的抽象方法,這樣就給子類型如何具體實(shí)現(xiàn)該方法留下了巨大的自由,既可以用方法實(shí)現(xiàn),又可以用 val 變量實(shí)現(xiàn)。[P107]
sealed abstract class HttpMethod() {
def body: String
def bodyLength: body.length
}
編譯器無法判斷 Enumeration 相應(yīng)的 match 語句是否全覆蓋。[P107]
定義變量:
val Person(name, age, Address(_, state, _)) = Person("Dean", 29, Address("1 Scala Way", "CA", "USA))
// 得到 name, age, state;
val head +: tail = List(1, 2, 3)
// head: Int = 1
// tail: List[Int] = List(2, 3)
val Seq(a, b, c) = List(1, 2, 3)
// 得到 a, b, c
val Seq(a, b, c) = List(1, 2, 3, 4) // MatchError
在 if 中也可以使用模式匹配,但不能用 _ 占位符。[P108]
val p = Person("Dean", 29, Address("1 Scala Way", "CA", "USA"))
if (p == Person("Dean", 29, Address("1 Scala Way", "CA", "USA"))) "yes" else "no" // "yes"
Scala 對(duì)一些非字母數(shù)字的字符做了”字符映射“,使得他們符合 JVM 規(guī)范。比如: = 會(huì)被映射為 $eq 。[?P108]
元組:[P109]
def sum_count(ints: Seq[Int]) = (ints.sum, ints.size)
val (sum, count) = sum_count(List(1, 2, 3, 4, 5))
// sum: Int = 15
// count: Int = 5
在帶復(fù)雜參數(shù)的函數(shù)字面量中使用:[P109 - 110]
case class Address(street: String, city: String, country: String)
case class Person(name: String, age: Int)
val as = Seq(
Address("1 Scala Lane", "Anytown", "USA"),
Address("2 Clojure Lane", "Othertown", "USA"))
val ps = Seq(
Person("Buck Trends", 29)
Person("Clo Jure", 28)
)
val pas = ps zip as // Seq[(Person, Address)]
pas map {
case (Person(name, age), Address(street, city, country)) =>
s"$name (age: $age) lives at $street, $city, in $country"
}
在正則表達(dá)式中使用模式匹配去解構(gòu)字符串:[P110]
val cols = """\*|[\w, ]+"""
val table = """\w+"""
val tail = """.*"""
val selectRE = s"""SELECT\\s*(DISTINCT)?\\s+($cols)\\s*FROM\\s+($table)\\s*($tail)?;""".r
val selectRE(distinct1, cols1, table1, otherClauses) = "SELECT DISTINCT * FROM atable;"
/*
distinct1: String = DISTINCT
cols1: String = *
table1: String = atable
otherClauses: String = ""
*/
val selectRE(distinct2, cols2, table2, otherClauses) = "SELECT col1, col2 FROM atable;"
/*
distinct1: String = null
cols1: String = "col1, col2"
table1: String = atable
otherClauses: String = ""
*/
由于使用了變量插值,在正則表達(dá)式字符串中必須增加 \ 轉(zhuǎn)義。
要謹(jǐn)慎對(duì)待默認(rèn) case 子句:什么情況下才應(yīng)該出現(xiàn)“以上均不匹配”。[P111]