1. 程式人生 > >spark基礎-樣例類和模式匹配

spark基礎-樣例類和模式匹配

1、簡單示例
首先我們先做一個簡單的例子,假如我們需要一個操作算術表示式類庫,解決這個問題的第一步是輸入資料,為了簡單我們主要集中注意力在變數、數、二元、一元操作符組成的算術表示式上。那麼我們做一個簡單的程式:

abstract class Expd
case class Var(name:String) extends Expd
case class Number(num:Double) extends Expd
case class Unop(operator:String,arg:Expd) extends Expd
case class BinOp(operator:String,left:Expd,right:Expd) extends Expd

我們定義了一個抽象的基類和四個子類,每一個類都是我們要做的一種表示式,因為每個類的內部都沒有定義,所以省略了花括號。
在以上程式中我們看到在class前面加上了一個特殊的修飾符case,在scala裡,帶有這種修飾符的類叫做樣例類,這種類scala會幫我們新增一些語法上的便利。
第一、會給我們新增一個工廠方法,也就是說這種類的構造要比普通的類構造簡單一些,以Var為例,可以直接使用如下方式構造。

scala> val varValue=Var("testVar")
varValue: Var = Var(testVar)
scala> val testBinOp=BinOp("testBinOp",Number(1),Var("2.0"))
testBinOp: BinOp = BinOp(testBinOp,Number(1.0),Var(2.0))

而不再需要使用new的方式例項化,當然使用new的方式也不會有問題。
第二、引數列表的引數隱式都獲得了val屬性。因此這裡就直接作為了欄位處理。

scala> testBinOp.left
res0: Expd = Number(1.0)

第三、編譯器幫我們實現了toString、hashCode和equals方法。所以我們可以直接列印例項化的物件、直接對引數的值進行比較。
例如:

scala> println(testBinOp)
BinOp(testBinOp,Number(1.0),Var(2.0))
scala> testBinOp.left==Number(3)
res6: Boolean = false

而我們普通的類就不會有這些功能:
定義一個普通類

scala> class MyClass(str:String){}
defined class MyClass

例項化

scala> val my=new MyClass("ssd")
my: MyClass = [email protected]

列印例項化的結果,可以看到輸出的是一長串字母加數字。

scala> println(my)       
[email protected]

做比較告訴我們錯誤

scala> my.str=="ddfs"
<console>:13: error: value str is not a member of MyClass
       my.str=="ddfs"
          ^

第四、編譯器還會有一個copy方法,用於製作修改過的拷貝,能夠製作一個新的例項,這個例項除了一兩個屬性不一樣,其餘和元例項完全一樣。我們使用帶名字的引數給出修改,對於沒有給出的任何引數都使用老引數,實現如下:
s

cala> testBinOp.copy(operator="new ")
res9: BinOp = BinOp(new ,Number(1.0),Var(2.0))

scala> println(testBinOp)
BinOp(testBinOp,Number(1.0),Var(2.0))

我們觀察到只做了一個引數的修改,其他的都使用原來定義的引數。這些都是樣例類給我們帶來的好處,樣例類最大的好處是給我們提供了模式匹配。
我們現在想定義幾個表示式:負負取正、加0本身、乘1本身。大致過程如下

  def simlifyTop(expr:Expd):Expd=expr match{
    case Unop("-",Unop("-",e))=>e
    case BinOp("+",e,Number(0))=>e
    case BinOp("*",e,Number(1))=>e
    case _ => expr
  }

這裡用到了表示式match和java的switch非常像,但是match放在選擇器的後面。模式匹配包含case的可選分支,每一個case都是一個模式和一個或者多個表示式,如果模式匹配正確表示式就會被求值。=>用於把模式和表示式分開。
對以上方法進行呼叫:

scala> val unop:Expd=Unop("-",Unop("-",Number(2)))
unop: Expd = Unop(-,Unop(-,Number(2.0)))
scala> simlifyTop(unop)
res13: Expd = Number(2.0)

2、模式的種類
模式的語法很容易理解,所有的模式跟相應的表示式完全一樣。
統配模式
使用(_)表示匹配任何物件。下面展示了一個萬用字元的例子。

 def simlify(expr:Expd)=expr match{
    case BinOp(op,left,right)=>println(expr+" is a binary!")
case _ => 
  }

3、常量匹配
常量匹配僅匹配自己,例如2、false、"is"都是常量模式,任何val和單例物件都可以被當作常量使用,Nil能且只能匹配空列表。例項如下:

 def describe(a:Any)=a match{
    case 5=>false
    case true => "apple"
    case "pear"=>"yellow"
    case Nil=>"empty"
    case _ => "another"
  }

下面是呼叫:

scala>  describe(5)
res14: Any = false

scala>  describe(true)
res15: Any = apple

scala>  describe("no")
res17: Any = another

4、變數匹配
變數模式匹配任何物件。和萬用字元非常像。不同於通配模式,scala將對應的變數繫結成匹配上的物件。繫結之後就可以用這個變數來對物件進行匹配。

 def checkZero(z:Any)=z match{
    case 0=>"zero"
    case another=>"not zero" + another
  }

我們看到another就是一個變數,當來的任何除了0以外的值來的時候自動使用這種方式匹配,這裡和_有一個區別,我們這裡可以獲取到這個變數的值,也就是直接使用another接受了這個變數。

scala> checkZero(0)
res18: String = zero

scala> checkZero(" Not zero")
res20: String = not zero Not zero

我們看到收到並輸出了" Not zero"。
在看一個關於常量E和PI的例子:

import math.{E,PI}
scala> E match{
      case Pi=> "it is Pi"+Pi
      case _=>"OK"
      }
res21: String = OK

我們看一下res21: String = OK這個結果,這個結果表明只有String = OK被使用了,case Pi=> “it is Pi”+Pi沒有被採用,因為E永遠不能等於Pi。

我們知道E、Pi這些在scala內有固定值的。

scala> val pi=math.Pi
pi: Double = 3.141592653589793

直接用變數的方式得到E的值

scala> E match{
     case pi=>pi}
res23: Double = 2.718281828459045

如果我們採用了這種方式,然後在變數模式的下面新增一個模式,那麼就會出現問題。

scala> E match{
     | case pi=>pi
     | case _=> "yes"
     | }
<console>:14: warning: patterns after a variable pattern cannot match (SLS 8.1.1
)
       case pi=>pi
            ^
<console>:15: warning: unreachable code due to variable pattern 'pi' on line 14
       case _=> "yes"
                ^
<console>:15: warning: unreachable code
       case _=> "yes"
                ^
res24: Any = 2.718281828459045

問題告訴我們unreachable 這是一個不能被找到的模式,因為變數模式本身已經匹配了所有的方式,再在變數模式的下邊做一個模式是永遠也不能被匹配的。
5、構造方法模式
構造方法模式才是匹配模式的厲害之處。一個構造方法模式例如最開始的
case Unop("-",Unop("-",e))=>e。他是由一個名稱和一組引數組成,這是一個樣例類,這樣一種模式首先檢查被匹配的物件是否是以這個名稱命名的樣例類例項,然後在檢查這個物件的構造方法是否匹配這些給出的引數模式。

abstract class Expd
case class Var(name:String) extends Expd
case class Number(num:Double) extends Expd
case class Unop(operator:String,arg:Expd) extends Expd
case class BinOp(operator:String,left:Expd,right:Expd) extends Expd
  def simlifyTop(expr:Expd):Expd=expr match{
    case Unop("-",Unop("-",e))=>e
    case BinOp("+",e,Number(0))=>e
    case BinOp("*",e,Number(1))=>e
    case _ => expr
  }

這就是最好的一個構造方法模式的樣例。
6、序列、元組模式
匹配的時候使用序列或元組作為原則:

  def ListOrTuple(a:Any) =a match{
    case List(0,_,_)=> println("it is a List")
    case List(0,_*)=> println("it is a List")
    case Array(0,_*)=> println("it is a List")
    case Array(_,_)=> println("it is a List")
    case (x,y,z)=>println("it is a tuple"+x+y+z)
case (s,r)=>println("it is a tuple"+s)
    case another=>println("Not List and tuple " + another)
  }

List(0,,)是一個給定數值兩個沒給的List,List(0,*)是一個給一個數值,還有多個沒給的List,Array(0,*)一個給定多個沒給的Array。(x,y,z)三個變數的元組,(s,r)兩個個變數的元組。another一個變數模式的匹配,表示任何其他情況。