向Java開發者介紹Scala

發表於2011-09-27

注:本文轉載自InfoQ – 王恆濤

Scala結合了物件導向程式設計與函式程式設計思想,使用一種能夠完全相容Java、可以執行在Java虛擬機器上的、簡潔的語法。對於函式程式設計風格的支援,尤其是對於Lambda表示式的支援,能夠有助於減少必須要編寫的邏輯無關固定程式碼,也許讓它可以更簡單的關注要面對的任務本身,而相對的Java中對Lamdba表示式的支援要到預定於2012年釋出的JavaSE8才會實現。本文就是對於Scala介紹。

作為第一步,先安裝好最新的Scala釋出包Typesafe stack,開啟命令列視窗,鍵入“scala”:這會啟動REPL(讀入-運算 輸出 迴圈)互動式編碼環境。然後你就可以寫下你的第一行Scala程式碼:

我們剛剛宣告瞭一個型別為Int的變數,初始值為1492,就像我們在Java裡用語句Int columbus = 1492;所做的一樣。除了把型別放在變數之後這樣一種反向的宣告方式之外,Scala在這裡所表現出的不同是使用“val”來顯性地把變數宣告為不可變。如果我們想要修改這個變數:

請注意錯誤訊息精確地指出了錯誤位於行的哪個位置。再嘗試宣告這個變數,但這一次用“var”,讓其可變更。這樣編譯器擁有足夠的智慧來推斷出1492是一個整數,你也就不需要指定型別了:

接下來,我們來定義一個類:

我們定義了一個類,名為Employee,有三個不可變更的欄位:name、age和company,各自有自己的預設值。關鍵字“case”相當於Java裡的switch語句,只不過要更為靈活。它說明該類具有模式匹配的額外機制,以及其他一些特性,包括用來建立例項的工廠方法(不需要使用“new”關鍵字來構造),同樣的也不需要建立預設的getter方法。與Java中不同的是,變數預設下的訪問控制是public(而不是protected),而Scala為公開變數建立一個getter方法,並命名為變數名。如果你願意,你也可以把欄位定義成可變且/或私有(private)的,只需要在引數之前使用“var”(例如:case class Person(private var name:String))。

我們再來用不同方式建立一些例項,看看其他的特性,像是命名引數和預設引數(從Scala2.8開始引入):

不過,下面的寫法

是行不通的(可不是因為Darth不是DevCode的僱員!),這是由於建構函式在這個位置需要age作為引數,因為函式引數沒有顯性地進行命名。

現在我們再來看集合,這才是真正讓人興奮的地方。

有了泛型(Java5以上),Java可以遍歷一個——比方說整數型列表,用下面這樣的程式碼:

執行的結果是

Scala對於可變集合和不可變集合進行了系統性地區別處理,不過鼓勵使用不可變集合,也因此在預設情況下建立不可變集合。這些集合是通過模擬的方式實現新增、更新和刪除操作,在這些操作中,不是修改集合,而是返回新的集合。

與前面的Java程式碼等價的Scala程式碼可能像下面這樣:

這裡的“for”迴圈語法結構非常接近於Java的指令式程式設計風格。在Scala(以及Java虛擬機器上其他很多語言如:Groovy、JRuby或JPython)裡還有另外一種方式來實現上面的邏輯。這種方式使用一種更加偏向函式程式設計的風格,引入了Lambda表示式(有時也稱為閉包——closure)。簡單地說,Lambda表示式就是你可以拿來當作引數傳遞的函式。這些函式使用引數作為輸入(在我們的例子中就是“n”整型變數),返回語句作為函式體的最終語句。他們的形式如下

上面的例子中,函式體只有一條語句(println……),返回的是單位(Unit,也就是“空結果”),也就是大致相當於Java中的void,不過有一點不同的是——void是不返回任何結果的。
除了只列印出我們的數值列表以外,應該說我們更想做的是處理和變換這些元素,這時我們需要呼叫方法來生成結果列表,以便後面接著使用。讓我們嘗試一些例子:

最後的這一個變換“map”非常有用,它對列表的每一個元素應用閉包,結果是一個同樣大小的、包含了每個變換後元素的列表。

我們在這裡還想介紹最後的一個方法,就是“foldLeft”方法,它把狀態從一個元素傳播到另一個元素。比如說,要算出一個列表裡所有元素的和,你需要累加它們,並在切換元素的時候儲存中間的計數:

作為第一個變數傳遞給foldLeft的值0是初始值(也就是說在把函式用到第一個列表元素的時候total=0)。(total,element)代表了一個Tuple2,在Scala裡這是一個二元組(就像要表示三維空間座標,經常要用到Tuple3(x,y,z)等等)。注意在合計時,Scala的程式設計介面實際上提供了一個“sum”方法,這樣上一條語句就可以寫成:

還有許多其他的類似的集合變換方法,你可以參照scaladoc API。你也可以把這些方法組合起來(例如:numbers.reverse.filter……),讓程式碼更加簡潔,不過這樣會影響可讀性。

最後,{ n => n + 10 }還可以簡單地寫成(_ + 10),也就是說如果輸入引數只是用於你呼叫的方法,則不需要宣告它;在我們的例子裡,“n”被稱為匿名變數,因為你可以把它用任何形式來代替,比如說“x”或者“number”,而下劃線則表示一處需要用你的列表的每個元素來填補的空白。(與“_”的功能類似,Groovy保留了關鍵字“it”,而Python則使用的是“self”)。

在介紹了對整數的基本處理後,我們可以邁入下一個階段,看看複雜物件集合的變換,例如使用我們上面所定義的Employee類:

從這個五個元素的列表裡,我們可以應用一個條件來過濾出應用匿名方法後返回值為True的僱員,這樣就得到了——比方說屬於DevCode的僱員:

假設我們手頭的allEmployees集合是我們使用SQL查詢獲得的結果集,查詢語句可能類似於“SELECT * FROM employees WHERE company = ‘DevCode’ ”。現在我們可以把List[Employee]變換到以company名稱作為鍵、屬於該公司的所有員工的列表作為值的Map型別,這樣就可以把僱員按company來排序:

每一個列表已經作為一個值存入了(鍵——值)雜湊表,為了示範如何進一步處理這些列表,可以設想我們需要計算每個公司的僱員平均年齡。

這具體意味著我們必須要計算每個列表的每個僱員的的“age”欄位的和,然後除以該列表中僱員的數量。讓我們先計算一下DevCode:

回到我們的Map (key:String ->value:List[Employee]),下面是個更加一般性的例子。我們現在可以歸併並計算每個公司的平均年齡,要做的只是寫幾行程式碼:

這裡的“case(key,value)”說明了Scala提供的模式匹配機制是多麼強大。請參考Scala的文件來獲取更多的資訊。

到這裡我們的任務就完成了。我們實現的是一個簡單的Map-Reduce演算法。由於每個公司僱員的歸併是完全獨立於其他公司,這個演算法非常直觀地實現了平行計算。

相關文章