Java 下一代: Groovy、Scala 和 Clojure

在與 Martin Fowler 共同參加的一次主題演講中,他提供了一個敏銳的觀察報告: html

Java 的遺產是  平臺,不是  語言

最初的 Java 技術工程師曾作過一個了不得的決定,將語言從運行時中分離出來,最終使 200 多種語言可在 Java 平臺上運行。該基礎架構對平臺保持長久活力很是關鍵,由於計算機編程語言的壽命一般很短。自 2008 年以來,每一年由 Oracle 主辦的 JVM 語言峯會都會爲 JVM 上替代語言的實現者提供與平臺工程師公開合做的機會。 java

歡迎來到 Java 下一代專欄系列。在這裏,我將簡要介紹三種現代 JVM 語言:Groovy、Scala 和 Clojure,它們將範式、設計選擇和溫馨因素進行了有趣的組合。我不打算在這裏詳細介紹每種語言;它們各自的網站上都有這方面的介紹(參閱 參考資料)。但語言社區網站(主要目的是福音傳道)上沒有提供語言不適應的客觀信息或任務示例。在本系列文章中,我將進行實質性對比,幫助填補這項空白。本文準備概述 Java 下一代語言以及學習這些語言的好處。 ios

超越 Java

Java 語言因 Bruce Tate 在其著做 超越 Java(參閱 參考資料)中將其稱爲 完美風暴而出名,致使 Java 出名的綜合因素包括:Web 的興起、現有 Web 技術因各類緣由產生的不適應性,以及企業多層應用開發的興起。Tate 也認爲完美風暴是一系列獨立事件,而其餘語言不會以一樣方式達到一樣的高度。 程序員

Java 語言已證實其功能至關靈活,但人所共知,其語法和固有範式具備必定的侷限性。儘管 Java 語言正在進行一些看似美好的改變,但其語法根本不支持一些重要的將來目標,如函數式編程元素。可是,若是您試圖找到一種新語言取代 Java,那您就錯了。 編程

多語言編程

多語言編程是我在 2006 年的一片博客文章中從新提出並推廣的一個術語(參閱 參考資料),多語言編程以單一語言並不適合解決全部問題的認知爲基礎的。一些語言具備更適合某些特定問題的內在特性。例如,雖然 Swing 與 Java 同樣成熟,但開發人員發如今 Java 中編寫 Swing UI 很是麻煩,由於它要求進行類型聲明,要求行爲具備匿名內部類,而且具備其餘衝突因素。使用更適合構建 UI 的語言,好比帶有SwingBuilder的 Groovy(參閱 參考資料),就會使構建 Swing 應用程序變得更容易。 緩存

JVM 上運行的語言的擴展使多語言編程的構思更具吸引力,由於您能夠在維護相同的底層字節碼和庫時將其混搭。例如,SwingBuilder不能取代 Swing;它在現有 Swing API 上進行分層。固然,長期以來,開發人員一直混合使用 JVM 之外的語言(例如,混搭使用 SQL 和 JavaScript 來實現特定目的),這在 JVM 範圍內更加廣泛。許多 ThoughtWorks 項目包含多種語言,ThoughtWorks Studios 開發的全部工具均使用混合語言。 架構

即便 Java 依舊是您的主要開發語言,也能夠了解如何運行其餘語言,以便在策略上使用它們。Java 依然是 JVM 生態系統的重要部分,但最終人們更傾向於將它用做平臺彙編語言 —一個您能夠徹底瞭解性能或知足特定需求的地方。 併發

發展

20 世紀 80 年代初,在我上大學期時,咱們使用稱爲 Pecan Pascal 的開發環境。它的獨特之處是能夠同時在 Apple II 或 IBM PC 上運行相同的 Pascal 代碼。Pecan 工程師使用某種稱爲 「字節碼」 的神祕東西實現了這一壯舉。開發人員將他們的 Pascal 代碼編譯成這種 「字節碼」,並在爲每一個平臺本地編寫的 「虛擬機」 上運行。多麼可怕的經歷啊!甚至對於簡單的任務而言,生成代碼也極其緩慢。當時的硬件根本沒法應對這種挑戰。 框架

發佈 Pecan Pascal 以後的十年,Sun 發佈了 Java,Java 使用了相同的架構,對於 20 世紀 90 年代中期的硬件環境,運行該代碼顯得有些緊張,但最終取得了成功。Java 還增長了其餘開發人員友好的特性,如自動垃圾收集。使用過像 C++ 這樣的語言以後,我不再想在沒有垃圾收集的語言中編寫代碼。我寧願花將時間花在更高層次上的抽象上,思考解決複雜業務問題的方法,也不肯意在內存管理等複雜的管道問題上浪費時間。 jsp

計算機語言一般不會有很長的壽命,緣由之一是語言和平臺設計的創新速度。隨着咱們的平臺逐漸強大,能夠處理的繁重做業也就越多。例如,Groovy 的 備忘特性(2010 年增長的特性)緩衝了函數調用結果。不須要手寫緩衝代碼,這可能會引入 bug,只需調用 memoize()方法便可,如清單 1 所示:

清單 1. 在 Groovy 中備忘一個函數
def static sum = { number -> 
  factorsOf(number).inject(0, {i, j -> i + j}) 
 } 
 def static sumOfFactors = sum.memoize()

清單 1 中,sumOfFactors方法的結果是自動緩存的。您也可使用另外一種方法自定義緩衝行爲,好比 memoizeAtLeast()和memoizeAtMost()。Clojure 還提供了備忘功能,這對 Scala 的實現是無足輕重的。下一代語言(以及一些 Java 框架)中的高級特性(好比備忘功能)將逐漸找到它們進入 Java 語言的方法。Java 的下一個版本將增長高階函數,使備忘功能的實現變得更容易。經過學習下一代 Java 語言,提早了解將來 Java 特性。


Groovy、Scala 和 Clojure

Groovy 是 21 世紀的 Java 語法(濃縮咖啡,而非普通咖啡)。Groovy 的設計目標是更新並減小 Java 語法阻力,同時支持 Java 語言中的主要範式。所以,Groovy 須要 「瞭解」 JavaBeans 這類技術,並簡化屬性訪問。Groovy 快速合併新特性,並提供了一些重要函數特性,我將在後面幾期中重點介紹。Groovy 在根本上依然是面向對象的命令式語言。Groovy 與 Java 的兩個主要區別是,Groovy 是 靜態而非動態類型,並且它的元程序功能更佳。

Scala 是一種充分利用了 JVM 優點的語言,但其語法徹底進行了從新設計。Scala 是一種強靜態類型語言(比對類型要求比較嚴格的 Java 更嚴格)支持面向對象範式和函數範式,並且更青睞於後者。例如,Scala 傾向 val聲明,並使不變的變量(相似於在 Java 中將參數標記爲final)服從於 var,這建立了人們更加熟悉的可變變量。經過大力支持這兩種範式,Scala 爲您提供了從您多是(一名面向對象的命令式程序員)到可能應該是(一名傾向函數式的程序員)的橋樑。

Clojure 是一種 Lisp 方言,在語法上完全背離了其餘語言。它是一種強動態類型語言(和 Groovy 同樣),反映了獨斷的設計決策。雖然 Clojure 容許您用遺留 Java 進行完整和深刻的交互操做,但它並不試圖構建與舊式範式相連的橋樑。例如,Clojure 不具有糾錯功能,而且支持面向對象進行交互操做。可是,它還支持對象程序員所習慣的全部特性,如多態性,但它以函數方式而非面向對象的方式來實現這些特性。Clojure 圍繞一些核心工程原理(好比 Software Transactional Memory)進行設計,它打破了舊的範式,支持新的功能。


範式

除了語法以外,這些語言最有趣的不一樣之處在於類型和底層主要範式:函數式或命令式。

靜態與動態類型

編程語言中的 靜態類型指的是顯式類型聲明,如 Java 的 int x;聲明。動態類型指的是不要求提供類型聲明信息的語言。全部語言都是 類型語言,這意味着您的代碼能夠反映賦值後的類型。

Java 的類型系統飽受責備,由於它的靜態類型帶來了許多不便,並且沒法得到較大收益。例如,在現行的有限類型推斷以前,Java 要求開發人員在賦值等號的兩側重複進行類型聲明。Scala 與 Java 相比是更加靜態的類型,但在平常使用時並不麻煩,由於它很好地利用了類型推斷。

Groovy 有一個行爲,乍一看,該行爲彷佛架起了靜態與動態之間的橋樑。請考慮清單 2 所示的簡單集合工廠:

清單 2. Groovy 集合工廠
class CollectionFactory { 
  def List getCollection(description) { 
    if (description == "Array-like") 
      new ArrayList() 
    else if (description == "Stack-like") 
      new Stack() 
  } 
 }

清單 2 中的類充當了一個工廠,它將根據傳遞的 description 參數返回兩個 list 接口實現程序中的一個(ArrayList 或 Stack)。對 Java 開發人員而言,這彷佛可以確保返回的結果知足規則。可是,清單 3 中的這兩個單元測試顯示了它帶來的併發症:

清單 3. 測試 Groovy 中的集合類型
@Test 
 void test_search() { 
  List l = f.getCollection("Stack-like") 
  assertTrue l instanceof java.util.Stack 
  l.push("foo") 
  assertThat l.size(), is(1) 
  def r = l.search("foo") 
 } 

 @Test(expected=groovy.lang.MissingMethodException.class) 
 void verify_that_typing_does_not_help() { 
  List l = f.getCollection("Array-like") 
  assertTrue l instanceof java.util.ArrayList 
  l.add("foo") 
  assertThat l.size(), is(1) 
  def r = l.search("foo") 
 }

在 清單 3 的第一個單元測試中,我經過工廠檢索 Stack,驗證它確實是 Stack,而後執行 Stack的操做,如 push()、size()和 search()。但在第二個單元測試中,我必須用 MissingMethodException 預期發生的異常,以保護測試,這樣才能經過測試。在檢索 Array-like 集合並將它做爲一個 List 放入變量類型時,我能夠證實我確實收到了一個 list。可是,當我試圖調用這個 search()方法時,它會觸發異常,由於ArrayList不包括 search()方法。所以,經過提供無編譯時間保護的聲明,能夠確保方法調用是正確的。

雖然這看起來可能像一個 bug,但它倒是正確的行爲。Groovy 中的類型只確保 賦值語句的有效性。例如,在 清單 3 中,若是我返回一些 List接口的內容,則會觸發運行時異常(GroovyCastException)。這會讓 Groovy 與 Clojure 在強動態類型家族中緊緊站穩腳跟。

然而,語言最近發生的變化已使 Groovy 中的靜態和動態區分變得十分模糊。Groovy 2.0 新增了一個 @TypeChecked註釋,使您可以在類或方法級別上制定嚴格類型檢查特別決策。清單 4 舉例說明了這個註釋:

清單 4. 類型檢查與註釋
@TypeChecked 
 @Test void type_checking() { 
    def f = new CollectionFactory() 
    List l = f.getCollection("Stack-like") 
    l.add("foo") 
    def r = l.pop() 
    assertEquals r, "foo"
 }

在 清單 4 中,我添加了 @TypeChecked 註釋,驗證了賦值和隨後的方法調用。例如,清單 5 中的代碼再也不進行 編譯

清單 5. 類型檢查可防止無效的方法調用
@TypeChecked 
 @Test void invalid_type() { 
    def f = new CollectionFactory() 
    Stack s = (Stack) f.getCollection("Stack-like") 
    s.add("foo") 
    def result = s.search("foo") 
 }

在 清單 5 中,我必須爲工廠返回添加類型轉換,以支持我調用 Stack的 search()方法。該工具與限制條件一塊兒使用:在啓用靜態類型時,Groovy 的許多動態特性是無效的。可是,該示例說明了 Groovy 在創建靜態和動態劃分之間的橋樑時的不斷變化。

全部這些語言都是十分強大的元程序編程工具,因此過後能夠添加更加嚴格的類型。例如,有一些輔助項目能夠將選擇類型添加到 Clojure。可是,一般狀況下,若是選擇類型是可選的,那麼它就再也不是類型系統的一部分;它是一種驗證機制。

命令式與函數式

另外一個主要比照是命令式與函數式。命令式編程主要關注分步指令,在許多狀況下,模仿古老的低級硬件的便利條件。函數式編程更關注一流結構函數,並試圖最大程度地減小狀態轉換和易變性。

受 Java 影響的比較大的 Groovy 其實是一種命令式語言。但從一開始它就包含大量函數式語言特性,隨着時間的推移,還在不斷添加新的特性。

Scala 將這兩種範式結合起來使用併爲它們提供支持。雖然函數式編程更受喜歡(並受支持),但 Scala 仍然支持面向對象的編程和命令式編程。所以,恰當使用 Scala 要求一個紀律嚴明的團隊確保您不會將範式隨意混合搭配,這在多範式語言中一直是一種危險舉動。

Clojure 沒有提供糾錯功能。它支持面向對象的編程,以便支持與其餘 JVM 語言輕鬆交互,但它並不會充當橋樑。相反,Clojure 的獨斷設計聲明設計師所思考的正是良好的工程實踐。那些決策影響深遠,使 Clojure 可以以開創性的方式解決 Java 世界冗繁的問題(如併發性)。

瞭解這些全新語言所需的諸多思惟轉換都來自命令式 / 函數式劃分,並且這是本系列文章中最有意義的探索領域之一。


結束語

開發人員生活在一個採用多種語言解決問題的語言種類愈來愈多的世界中。學會有效使用新語言有助於您肯定某種方法什麼時候適用。您無需拋棄 Java,它將逐漸融合下一代 JVM 語言的特性;經過了解它們,您能夠窺視 Java 語言的將來。

在下一期的 Java 下一代語言中,我將開始對比 Groovy、Scala 和 Clojure 之間的共同之處。

相關文章
相關標籤/搜索