本專題側(cè)重介紹 Scala 的 case class 和 pattern matching (模式匹配),這倆個兩個程序結(jié)構(gòu)對于處理樹結(jié)構(gòu)的數(shù)據(jù)非常有幫助。 Scala 的 case class 使得對對象進行模式匹配變得非常方便,簡單的來說,Scala 的 case clas s就是在普通的類定義前加 case 這個關(guān)鍵字,然后你可以對這些類來模式匹配。
在我們詳細介紹 Scala 的 Case class 和模式匹配之前,我們可以通過一個簡單的例子來說明一些基本概念。我們設(shè)計一個函數(shù)庫,這個函數(shù)庫可以用來計算算術(shù)表達式,為簡單起見,我們設(shè)計的算術(shù)表達式只側(cè)重于變量,數(shù)字,單操作符,和雙操作符。我們可以采用如下的 Scala 類定義:
abstract class Expr
case class Var(name:String) extends Expr
case class Number(num:Double) extends Expr
case class UnOp(operator:String, arg:Expr) extends Expr
case class BinOp(operator:String,left:Expr,right:Expr) extends Expr
這里我們定義了一個抽象類 Expr 和四個子類(分別代表變量,數(shù)值,單操作符,雙操作符),Scala 允許我們不定義類的實現(xiàn),實際我們是 class C 和 class C {} 是等價的。
我們可以看到上面的四個子類定義前面我們使用了 case 關(guān)鍵字,使用了 case 關(guān)鍵字的類定義就是case classes。使用這個關(guān)鍵字,Scala 編譯器會自動為你定義的類生成一些成員。
首先,編譯器為 case class 生成一個同名的對象構(gòu)造器(Factory Method),也就是你可以使用 Var(“x”) 來創(chuàng)建一個類的實例,而無需使用 new Var(“x”)。
scala> val x = Var("x")
x: Var = Var(x)
這個構(gòu)造器在嵌套使用時顯得非常簡潔明了,比如我們構(gòu)建如下的表達式,這種寫法避免了很多 new 的使用。
scala> val op=BinOp("+",Number(1),x)
op: BinOp = BinOp(+,Number(1.0),Var(x))
其次,Scala 編譯器為 case class 的構(gòu)造函數(shù)的參數(shù)創(chuàng)建以參數(shù)名為名稱的屬性,比如 Val 的類的參數(shù) name:String 可以直接通過 .name 訪問,比如:
scala> x.name
res1: String = x
第三,編譯器為 case class 構(gòu)造了更自然的 toString,hashCode 和 equals 實現(xiàn),它們會遞歸打印,比較 case class 的參數(shù)屬性。比如:
scala> println(op)
BinOp(+,Number(1.0),Var(x))
scala> op.right == Var("x")
res3: Boolean = true
最后一點,Scala 編譯器為 case class 添加了一個 Copy 方法,這個 copy 方法可以用來構(gòu)造類對象的一個可以修改的拷貝。這對于使用已有的對象構(gòu)造一個新實例非常方便,你只要修新創(chuàng)建實例的某些參數(shù)即可。 比如我們想創(chuàng)建一個和 op 類似的新的實例,只想修改+為-,可以使用:
scala> op.copy(operator="-")
res4: BinOp = BinOp(-,Number(1.0),Var(x))
以上這些慣例帶來了很多便利,這些便利也需要一些小小的代價,就是需要在 class 前面使用 case 關(guān)鍵字,而構(gòu)造后的類由于自動添加了一些方法而變大了些。case class 帶來的最大的好處是它們支持模式識別。
比如說你需要簡化表達式的表示方法,這里給出一個簡單的規(guī)則:
UnOp(“-“,Unop(“-“,e)) => e//負負得正
BinOp(“+”,e,Number(0)) => e//和0加
BinOp(”*”,e,Number(1)) => e //和1乘
使用模式匹配,在Scala我們幾乎和使用和上面規(guī)則非常類似的代碼來實現(xiàn)表達式的簡化:
scala> def simplifyTop(expr :Expr) :Expr = expr match {
| case UnOp("-",UnOp("-",e))=>e
| case BinOp("+",e,Number(0))=>e
| case BinOp("*",e,Number(1))=>e
| case _ => expr
|
| }
simplifyTop: (expr: Expr)Expr
scala> simplifyTop(UnOp("-",UnOp("-",Var("x"))))
res6: Expr = Var(x)
scala>
simplifyTop 定義使用了 match 表達式,它對應(yīng) Java 的 switch 語句,但它的語法和 switch 不同,它的 selector 在 match 前面:
selector match { alternatives}
一個模式匹配由多個可選項組成,沒個選項由 case 開始,每個選項定義一個模式,每個模式對應(yīng)一個表達式,表達式應(yīng)用到當(dāng)模式匹配的時候。模式和表達式之間使用=>分隔。
一個 match 表達式的結(jié)果取決于第一個匹配的可選項,當(dāng)該項模式匹配時,該模式 => 后的表達式被選中然后計算該表達式的值。
一個常量模式,比如本例中的 “+” 和 “0” ,匹配對應(yīng)的常數(shù),一個變量模式比如 e,可以匹配任意的值。然后=>右邊的表示式可以應(yīng)用這個變量,“_”為通配符,可以匹配任意的值。
構(gòu)造器模式,比如 UnOp(“-“,e),可以匹配 UnOp 類型的值,這個 UnOp 的第一個參數(shù)為”-“,第二個參數(shù)可以為任意。
和 Java 的 switch 語句比較,Scala 的 match 有以下幾個不同點: