天天看點

大資料Scala系列之模式比對和樣例類

大資料Scala系列之模式比對和樣例類

1.樣例類

在Scala中樣例類是一中特殊的類,樣例類是不可變的,

可以通過值進行比較,可用于模式比對。

定義一個樣例類:

1.構造器中每一個參數都是val,除非顯示地聲明為var

2.伴生對象提供apply ,讓你不使用new關鍵字就能構造出相應的對象

case class Point(x: Int, y: Int)

建立樣例類對象:

val point = Point(1, 2)

val anotherPoint = Point(1, 2)

val yetAnotherPoint = Point(2, 2)

//通路對象值

point.x

point.x =1 //不可以

通過值對樣例類對象進行比較:

if (point == anotherPoint) {

println(point + " and " + anotherPoint + " are the same.")

} else {

println(point + " and " + anotherPoint + " are different.")

}

// Point(1,2) 和 Point(1,2)一樣的.

if (point == yetAnotherPoint) {

println(point + " and " + yetAnotherPoint + " are the same.")

println(point + " and " + yetAnotherPoint + " are different.")

// Point(1,2)和Point(2,2)是不同的.

樣例類的拷貝

You can create a (shallow) copy of an instance of a case class simply by using the copy method. You can optionally change the constructor arguments.

case class Message(sender: String, recipient: String, body: String)

val message4 = Message("[email protected]", "[email protected]", "Me zo o komz gant ma amezeg")

val message5 = message4.copy(sender = message4.recipient, recipient = "[email protected]")

message5.sender // [email protected]

message5.recipient // [email protected]

message5.body // "Me zo o komz gant ma amezeg"

在模式比對中使用樣例類:

abstract class Amount

// 繼承了普通類的兩個樣例類

case class Dollar(value: Double) extends Amount

case class Currency(value: Double, unit: String) extends Amount

case object Nothing extends Amount

object CaseClassDemo {

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

val amt = new Dollar(10);
patternMatch(amt)           

def patternMatch(amt: Amount) {

amt match {
  case Dollar(v) => println("$" + v)
  case Currency(_, u) => println("Oh noes, I got " + u)
  case Nothing => println("nothing") //樣例對象沒有()
}           

聲明樣例類 ,以下幾件事會自動發生:

1.提供unapply方法,讓模式比對可以工作

2.生成toString equals hashCode copy 方法,除非顯示給出這些方法的定義。

2.模式比對

1.更好的switch

Scala中類似Java的switch代碼:

注意:

Scala的模式比對隻會比對到一個分支,不需要使用break語句,因為它不會掉入到下一個分支。 match是表達式,與if表達式一樣,是有值的:

object PatternDemo {

var sign = 0
val ch: Char  = 'p'
val valchar = 'p'
var digit = 0
           

//match 是表達式

ch match {
  case '+' => sign = 1
  case '-' => sign = -1
  //使用|分割多個選項
  case '*' | 'x' => sign = 2
  //可以使用變量
  //如果case關鍵字後面跟着一個變量名,那麼比對的表達式會被指派給那個變量。
  case valchar => sign = 3
  //case _ 類似Java中的default
  // 如果沒有模式能比對,會抛出MacthError
  //可以給模式添加守衛
  case _ if Character.isDigit(ch) => digit = Character.digit(ch, 10)
}
println("sign = "+ sign)           

1常量模式(constant patterns) 包含常量變量和常量字面量

scala> val site = "alibaba.com"

scala> site match { case "alibaba.com" => println("ok") }

scala> val ALIBABA="alibaba.com"

//注意這裡常量必須以大寫字母開頭

scala> def foo(s:String) { s match { case ALIBABA => println("ok") } }

常量模式和普通的 if 比較兩個對象是否相等(equals) 沒有差別,并沒有感覺到什麼威力

2 變量模式(variable patterns)

确切的說單純的變量模式沒有比對判斷的過程,隻是把傳入的對象給起了一個新的變量名。

scala> site match { case whateverName => println(whateverName) }

上面把要比對的 site對象用 whateverName 變量名代替,是以它總會比對成功。不過這裡有個約定,對于變量,要求必須是以小寫字母開頭,否則會把它對待成一個常量變量,比如上面的whateverName 如果寫成WhateverName就會去找這個WhateverName的變量,如果找到則比較相等性,找不到則出錯。

變量模式通常不會單獨使用,而是在多種模式組合時使用,比如

List(1,2) match{ case List(x,2) => println(x) }

裡面的x就是對比對到的第一個元素用變量x标記。

3 通配符模式(wildcard patterns)

通配符用下劃線表示:"_" ,可以了解成一個特殊的變量或占位符。 單純的通配符模式通常在模式比對的最後一行出現,case _ => 它可以比對任何對象,用于處理所有其它比對不成功的情況。 通配符模式也常和其他模式組合使用:

scala> List(1,2,3) match{ case List(_,_,3) => println("ok") }

上面的 List(_,_,3) 裡用了2個通配符表示第一個和第二個元素,這2個元素可以是任意類型 通配符通常用于代表所不關心的部分,它不像變量模式可以後續的邏輯中使用這個變量。

4.樣例類比對

//定義樣例類

abstract class Notification

case class Email(sender: String, title: String, body: String) extends Notification

case class SMS(caller: String, message: String) extends Notification

case class VoiceRecording(contactName: String, link: String) extends Notification

//基于樣例類的模式比對

def showNotification(notification: Notification): String = {

notification match {

case Email(email, title, _) =>
  s"You got an email from $email with title: $title"
case SMS(number, message) =>
  s"You got an SMS from $number! Message: $message"
case VoiceRecording(name, link) =>
  s"you received a Voice Recording from $name! Click the link to hear it: $link"           

val someSms = SMS("12345", "Are you there?")

val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")

println(showNotification(someSms)) //結果:You got an SMS from 12345! Message: Are you there?

println(showNotification(someVoiceRecording)) //結果:you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123

2.帶守衛的模式

增加布爾表達式或者條件表達式使得比對更具體。

def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {

//僅比對email在importantPeople清單裡的内容

case Email(email, _, _) if importantPeopleInfo.contains(email) =>
  "You got an email from special someone!"
case SMS(number, _) if importantPeopleInfo.contains(number) =>
  "You got an SMS from special someone!"
case other =>
  showNotification(other) // nothing special, delegate to our original showNotification function           

val importantPeopleInfo = Seq("867-5309", "[email protected]")

val someSms = SMS("867-5309", "Are you there?")

val importantEmail = Email("[email protected]", "Drinks tonight?", "I'm free after 5!")

val importantSms = SMS("867-5309", "I'm here! Where are you?")

println(showImportantNotification(someSms, importantPeopleInfo))

println(showImportantNotification(someVoiceRecording, importantPeopleInfo))

println(showImportantNotification(importantEmail, importantPeopleInfo))

println(showImportantNotification(importantSms, importantPeopleInfo))

5.類型比對

可以對表達式類型進行比對:

val arr = Array("hs", 1, 2.0, 'a')
val obj = arr(Random.nextInt(4))
println(obj)
obj match {
  case x: Int => println(x)
  case s: String => println(s.toUpperCase)
  case _: Double => println(Int.MaxValue)
  case _ => 0
}           

注意: 當你在比對類型的時候,必須給出一個變量名,否則你将會拿對象本身來進行比對:

obj match {

case _: BigInt => Int.MaxValue // 比對任何類型為BigInt的對象

case BigInt => -1 // 比對類型為Class的BigInt對象

比對發生在運作期,Java虛拟機中泛型的類型資訊是被擦掉的。是以,你不能用類型來比對特定的Map類型。

case m: Map[String, Int] => ... // error

// 可以比對一個通用的映射

case m: Map[_, _] => ... // OK

// 但是數組作為特殊情況,它的類型資訊是完好的,可以比對到Array[Int]

case m: Array[Int] => ... // OK

3.比對數組、清單、元組

數組比對

val arr1 = Array(1,1)

val res = arr1 match {

case Array(0) => "0"

//比對包含0的數組

case Array(x, y) => s"$x $y"

// 比對任何帶有兩個元素的數組,并将元素綁定到x和y

case Array(0, _*) => "0..."

//比對任何以0開始的數組

case _ => "something else"

清單比對

val lst = List(1,2)

val res2 = list match {

case 0 :: Nil => "0"

case x :: y :: Nil => x + " " + y

case 0 :: tail => "0 ..."

case _ => "something else"

}

元組比對

var pair = (1,2)

val res3 = pair match {

case (0, _) => "0 ..."

case (y, 0) => s"$y 0"

case _ => "neither is 0"

4.Sealed 類(密封類,備選)

Scala中,Traits 和classes可以被關鍵字sealed修飾, 修飾,被該關鍵字修飾後,它所有的子類都必須在同一檔案中被定義。

這樣做的好處是:當你用樣例類來做模式比對時,你可以讓編譯器確定你已經列出了所有可能的選擇,編譯器可以檢查模式語句的完整性。

sealed abstract class Furniture

case class Couch() extends Furniture

case class Chair() extends Furniture

//此時無需定義能比對所有的類型了

def findPlaceToSit(piece: Furniture): String = piece match {

case a: Couch => "Lie on the couch"

case b: Chair => "Sit on the chair"