摘要在歷史上Java; 平台一直屬於面向對象編程的領域但是現在甚至 Java 語言的堅定支持者也開始注意應用程序開發中的一種新趨勢函數編程在這個新的系列中Ted Neward 介紹了 Scala一種針對 JVM 將函數和面向對象技術組合在一起的編程語言在本文中Ted 將舉例說明您為何應該花時間學習 Scala(例如並發)並介紹如何快速從中受益
對於我來說她的名字是 Tabinda (Bindi) Khan那是一段愉快的少年時光准確地說是在七年級她很美麗聰明而最好的是她常常因我的笨拙的笑話而樂不可支在七年級和八年級的時間裡我們經常 出去走走(那時我們是這麼說的)但到了九年級我們分開了文雅一點的說法是她厭倦了連續兩年聽到同樣的笨拙的男孩笑話我永遠都不會忘記她(特別是因為我們在高中畢業 周年聚會時再次相遇)但更重要的是我將永遠不會失去這些珍貴的(也許有點言過其實)回憶
Java 編程和面向對象是許多程序員的 初戀我們對待它就像對待 Bindi 一樣尊重和完全的愛慕一些開發人員會告訴您 Java 將他們從內存管理和 C++ 的煉獄中解救出來了其他一些人會告訴您 Java 編程使他們擺脫了對過程性編程的絕望甚至對於一些開發人員來說Java 代碼中的面向對象編程就是 他們做事情的方式(嘿嘿如果這對我爸爸以及爺爺有用該多好!)
然而時間最終會沖淡所有對初戀的記憶生活仍然在繼續感情已經變了故事中的主角也成熟了(並且學會了一些新笑話)但最重要的是我們周圍的世界變了許多 Java 開發人員意識到盡管我們深愛 Java 編程但也應該抓住開發領域中的新機會並了解如何利用它們
關於本系列
Ted Neward 潛心研究 Scala 編程語言並帶您跟他一起徜徉在這個新的 developerWorks 系列 中您將深入了解 Scala並在實踐中看到 Scala 的語言功能在進行相關比較時Scala 代碼和 Java 代碼將放在一起展示但(您將發現)Scala 中的許多內容與您在 Java 編程中發現的任何內容都沒有直接關聯而這正是 Scala 的魅力所在! 畢竟如果 Java 代碼可以做到的話又何必學習 Scala 呢?
我將始終愛著你 ……
在最近五年中對 Java 語言的不滿情緒逐漸增多盡管一些人可能認為 Ruby on Rails 的發展是主要因素但是我要爭辯的是RoR(被稱為 Ruby 專家)只是結果而非原因或者可以更准確地說Java 開發人員使用 Ruby 有著更深刻更隱伏的原因
簡單地說Java 編程略顯老態了
或者更准確地說Java 語言 略顯老態了
考慮一下當 Java 語言最初誕生時Clinton(第一位)在辦公室中很少有人使用 Internet這主要是因為撥號是在家裡使用網絡的惟一方式博客還沒有發明出來每個人相信繼承是重用的基本方法我們還相信對象是為對世界進行建模的最好方法摩爾定律將永遠統治著世界
實際上摩爾定律引起了行業內許多人的特別關注自 / 年以來微處理器技術的發展使得具有多個 內核 的 CPU 得以創造出來本質上是一個芯片內具有多個 CPU這違背了摩爾定律摩爾定律認為 CPU 速度將每隔 個月翻一倍在兩個 CPU 上同時執行多線程環境而不是在單個 CPU 上執行標准循環周期這意味著代碼必須具有牢固的線程安全性才能存活下來
學術界已經展開了圍繞此問題的許多研究導致了過多新語言的出現關鍵問題在於許多語言建立在自己的虛擬機或解釋器上所以它們代表(就像 Ruby 一樣)到新平台的轉換並發沖突是真正的問題所在一些新語言提供了強大的解決方案太多的公司和企業對 年前從 C++ 到 Java 平台的遷移仍記憶猶新許多公司都不願意冒遷移到新平台的風險事實上許多公司對上一次遷移到 Java 平台仍心有余悸
了解 Scala
一種可伸縮語言
Scala 是一種函數對象混合的語言具有一些強大的優點
● 首先Scala 可編譯為 Java 字節碼這意味著它在 JVM 上運行除了允許繼續利用豐富的 Java 開源生態系統之外Scala 還可以集成到現有的 IT 環境中無需進行遷移
● 其次Scala 基於 Haskell 和 ML 的函數原則大量借鑒了 Java 程序員鐘愛的面向對象概念因此它可以將兩個領域的優勢混合在一起從而提供了顯著的優點而且不會失去我們一直依賴的熟悉的技術
● 最後Scala 由 Martin Odersky 開發他可能是 Java 社區中研究 Pizza 和 GJ 語言的最著名的人GJ 是 Java 泛型的工作原型而且它給人一種 嚴肅 的感覺該語言並不是一時興起而創建的它也不會以同樣的方式被拋棄
Scala 的名稱表明它還是一種高度可伸縮 的語言我將在本系列的後續文章中介紹有關這一特性的更多信息
下載並安裝 Scala
可以從 Scala 主頁 下載 Scala 包截止到撰寫本文時最新的發行版是 final它可以在 Java 安裝程序版本 RPM 和 Debian 軟件包 gzip/bz/zip 包中獲得可以簡單地將其解壓到目標目錄中而且可以使用源碼 tarball 從頭創建(Debian 用戶可以使用 aptget install 直接從 Debian 網站上獲得 版 版本具有一些細微的差異所以建議直接從 Scala 網站下載和安裝)
將 Scala 安裝到所選的目標目錄中 — 我是在 Windows? 環境中撰寫本文的所以我的目標目錄是 C/Prg/scalafinal將環境變量 SCALA_HOME 定義為此目錄將 SCALA_HOME\bin 放置於 PATH 中以便從命令行調用要測試安裝從命令行提示符中激發 scalac version它應該以 Scala 版本 final 作為響應
函數概念
開始之前我將列出一些必要的函數概念以幫助理解為何 Scala 以這種方式操作和表現如果您對函數語言 — HaskellML 或函數領域的新成員 F# — 比較熟悉可以 跳到下一節
函數語言的名稱源於這樣一種概念程序行為應該像數學函數一樣換句話說給定一組輸入函數應始終返回相同的輸出這不僅意味著每個函數必須返回一個值還意味著從一個調用到下一個調用函數本質上不得具有內蘊狀態(intrinsic state)這種無狀態的內蘊概念(在函數/對象領域中默認情況下指的是永遠不變的對象)是函數語言被認為是並發領域偉大的 救世主 的主要原因
與許多最近開始在 Java 平台上占有一席之地的動態語言不同Scala 是靜態類型的正如 Java 代碼一樣但是與 Java 平台不同Scala 大量利用了類型推斷(type inferencing)這意味著編譯器深入分析代碼以確定特定值的類型無需編程人員干預類型推斷需要較少的冗余類型代碼例如考慮聲明本地變量並為其賦值的 Java 代碼如清單 所示
清單 聲明本地變量並為其賦值的 Java 代碼
class BrainDead {
public static void main(String[] args) {
String message = Why does javac need to be told message is a String? +
What else could it be if Im assigning a String to it?;
}
}
Scala 不需要任何這種手動操作稍後我將介紹
大量的其他函數功能(比如模式匹配)已經被引入到 Scala 語言中但是將其全部列出超出了本文的范圍Scala 還添加許多目前 Java 編程中沒有的功能比如操作符重載(它完全不像大多數 Java 開發人員所想象的那樣) 具有 更高和更低類型邊界 的泛型視圖等與其他功能相比這些功能使得 Scala 在處理特定任務方面極其強大比如處理或生成 XML
但抽象概述並不夠程序員喜歡看代碼所以讓我們來看一下 Scala 可以做什麼
開始認識您
根據計算機科學的慣例我們的第一個 Scala 程序將是標准的演示程序 Hello World
Listing HelloScala
object HelloWorld {
def main(args: Array[String]): unit = {
Systemoutprintln(Hello Scala!)
}
}
使用 scalac Helloscala 編譯此程序然後使用 Scala 啟動程序(scala HelloWorld)或使用傳統的 Java 啟動程序運行生成的代碼注意將 Scala 核心庫包括在 JVM 的類路徑(java classpath %SCALA_HOME%\lib\scalalibraryjar HelloWorld)中不管使用哪一種方法都應出現傳統的問候
清單 中的一些元素對於您來說一定很熟悉但也使用了一些新元素例如首先對 Systemoutprintln 的熟悉的調用演示了 Scala 對底層 Java 平台的忠誠Scala 充分利用了 Java 平台可用於 Scala 程序的強大功能(事實上它甚至會允許 Scala 類型繼承 Java 類反之亦然但更多信息將在稍後介紹)
另一方面如果仔細觀察您還會注意到在 Systemoutprintln 調用的結尾處缺少分號這並非輸入錯誤與 Java 平台不同如果語句很明顯是在一行的末尾終結則 Scala 不需要分號來終結語言但是分號仍然受支持而且有時候是必需的例如多個語句出現在同一物理行時通常剛剛入門的 Scala 程序員不用考慮需不需加分號當需要分號的時候Scala 編譯器將提醒程序員(通常使用閃爍的錯誤消息)
此外還有一處微小的改進Scala 不需要包含類定義的文件來反映類的名稱一些人將發現這是對 Java 編程的振奮人心的變革那些沒有這樣做的人可以繼續使用 Java 類到文件 的命名約定而不會出現問題
現在看一下 Scala 從何處真正脫離傳統的 Java/面向對象代碼
將函數和表單最終結合起來
對於初學者Java 發燒友將注意到HelloWorld 是使用關鍵字 object 來定義的而不是使用 class這是 Scala 對單例模式(Singleton pattern)的認可 — object 關鍵字告訴 Scala 編譯器這將是個單例對象因此 Scala 將確保只有一個 HelloWorld 實例存在基於同樣的原因注意 main 沒有像在 Java 編程中一樣被定義為靜態方法事實上Scala 完全避開了 static 的使用如果應用程序需要同時具有某個類型的實例和某種 全局 實例則 Scala 應用程序將允許以相同的名字同時定義 class 和 object
接下來注意 main 的定義與 Java 代碼一樣是 Scala 程序可接受的輸入點它的定義雖然看起來與 Java 的定義不同實際上是等同的main 接受 String 數組作為參數且不返回任何值但是在 Scala 中此定義看起來與 Java 版本稍有差異args 參數被定義為 args Array[String]
在 Scala 中數組表示為泛型化的 Array 類的實例這正是 Scala 使用方括號([])而非尖括號(<>)來指明參數化類型的原因此外為了保持一致性整個語言中都使用 name type 的這種模式
與其他傳統函數語言一樣Scala 要求函數(在本例中為一個方法)必須始終返回一個值因此它返回稱為 unit 的 無值 值針對所有的實際目的Java 開發人員可以將 unit 看作 void至少目前可以這樣認為
方法定義的語法似乎比較有趣當它使用 = 操作符時就像將隨後的方法體賦值給 main 標識符事實上真正發生的事情是在函數語言中就像變量和常量一樣函數是一級概念所以語法上也是一樣地處理
您說的是閉包嗎?
函數作為一級概念的一個含義是它們必須被識別為單獨的結構也稱為閉包這是 Java 社區最近一直熱烈爭論的話題在 Scala 中這很容易完成考慮清單 中的程序此程序定義了一個函數該函數每隔一秒調用一次另一個函數
清單 Timerscala
object Timer
{
def oncePerSecond(): unit =
{
while (true)
{
Systemoutprintln(Time flies when youre having fun(ctionally))
Threadsleep()
}
}
def main(args: Array[String]): unit =
{
oncePerSecond
}
}
不幸的是這個特殊的代碼並沒有什麼功能 …… 或者甚至沒任何用處例如如果想要更改顯示的消息則必須修改 oncePerSecond 方法的主體傳統的 Java 程序員將通過為 oncePerSecond 定義 String 參數來包含要顯示的消息但甚至這樣也是極端受限的其他任何周期任務(比如 ping 遠程服務器)將需要各自版本的 oncePerSecond這很明顯違反了 不要重復自己 的規則我認為我可以做得更好
清單 Timerscala
object Timer
{
def oncePerSecond(callback: () => unit): unit =
{
while (true)
{
callback()
Threadsleep()
}
}
def timeFlies(): unit =
{ Consoleprintln(Time flies when youre having fun(ctionally)); }
def main(args: Array[String]): unit =
{
oncePerSecond(timeFlies)
}
}
現在事情開始變得有趣了在清單 中函數 oncePerSecond 接受一個參數但其類型很陌生形式上名為 callback 的參數接受一個函數作為參數只要傳入的函數不接受任何參數(以 () 指示)且無返回(由 => 指示)值(由函數值 unit 指示)就可以使用此函數然後請注意在循環體中我使用 callback 來調用傳遞的參數函數對象
幸運的是我在程序的其他地方已經有了這樣一個函數名為 timeFlies所以我從 main 中將其傳遞給 oncePerSecond 函數(您還會注意到timeFlies 使用了一個 Scala 引入的類 Console它的用途與 Systemout 或新的 javaioConsole 類相同這純粹是一個審美問題Systemout 或 Console 都可以在這裡使用)
匿名函數您的函數是什麼?
現在這個 timeFlies 函數似乎有點浪費 — 畢竟它除了傳遞給 oncePerSecond 函數外毫無用處所以我根本不會正式定義它如清單 所示
清單 Timerscala
object Timer
{
def oncePerSecond(callback: () => unit): unit =
{
while (true)
{
callback()
Threadsleep()
}
}
def main(args: Array[String]): unit =
{
oncePerSecond(() =>
Consoleprintln(Time flies oh you get the idea))
}
}
在清單 中主函數將一塊任意代碼作為參數傳遞給 oncePerSecond看起來像來自 Lisp 或 Scheme 的 lambda 表達式事實上這是另一種閉包這個匿名函數 再次展示了將函數當作一級公民處理的強大功能它允許您在繼承性以外對代碼進行全新地泛化(Strategy 模式的粉絲們可能已經開始唾沫橫飛了)
事實上oncePerSecond 仍然太特殊了它具有不切實際的限制即回調將在每秒被調用我可以通過接受第二個參數指明調用傳遞的函數的頻率來將其泛化如清單 所示
清單 Timerscala
object Timer
{
def periodicCall(seconds: int callback: () => unit): unit =
{
while (true)
{
callback()
Threadsleep(seconds * )
}
}
def main(args: Array[String]): unit =
{
periodicCall( () =>
Consoleprintln(Time flies oh you get the idea))
}
}
這是函數語言中的公共主題創建一個只做一件事情的高級抽象函數讓它接受一個代碼塊(匿名函數)作為參數並從這個高級函數中調用這個代碼塊例如遍歷一個對象集合無需在 for 循環內部使用傳統的 Java 迭代器對象而是使用一個函數庫在集合類上定義一個函數 — 通常叫做 iter 或 map — 接受一個帶單個參數(要迭代的對象)的函數例如上述的 Array 類具有一個函數 filter此函數在清單 中定義
清單 Arrayscala 的部分清單
class Array[A]
{
//
def filter (p : (A) => Boolean) : Array[A] = // not shown
}
清單 聲明 p 是一個接受由 A 指定的泛型參數的函數然後返回一個布爾值Scala 文檔表明 filter 返回一個由滿足謂詞 p 的數組的所有元素組成的數組這意味著如果我想返回我的 Hello World 程序查找所有以字母 G 開頭的命令行參數則可以編寫像清單 一樣簡單的代碼
清單 Hello Gmen!
object HelloWorld
{
def main(args: Array[String]): unit = {
argsfilter( (arg:String) => argstartsWith(G) )
foreach( (arg:String) => Consoleprintln(Found + arg) )
}
}
此處filter 接受謂詞這是一個隱式返回布爾值(startsWith() 調用的結果)的匿名函數並使用 args 中的每個元素來調用謂詞如果謂詞返回 true則它將此值添加到結果數組中遍歷了整個數組之後它接受結果數組並將其返回然後此數組立即用作 foreach 調用的來源此調用執行的操作就像它名字的含義一樣foreach 接受另一個函數並將此函數應用於數組中的每個元素(在本例中僅顯示每個元素)
不難想象等同於上述 HelloGscala 的 Java 是什麼樣的而且也不難發現 Scala 版本非常簡短也非常清晰
結束語
Scala 中的編程如此地熟悉同時又如此地不同相似之處在於您可以使用已經了解而且鐘愛多年的相同的核心 Java 對象但明顯不同的是考慮將程序分解成部分的方式在面向 Java 開發人員的 Scala 指南 的第一篇文章中我僅僅簡單介紹了 Scala 的功能將來還有很多內容尚待挖掘但是現在讓我們陶醉在函數化的過程中吧!
參考資料
您可以參閱本文在 developerWorks 全球站點上的 英文原文
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26916.html