Java.next: 下一代JVM語言
本文是ThoughtWorks公司架構(gòu)師Neal Ford在IBM developerWorks系列文章java.next中的***篇,其基于Groovy,Scala和Clojure,講述了多語言編程的重要性,并對(duì)靜態(tài)類型與動(dòng)態(tài)類型,函數(shù)式編程與命令式編程進(jìn)行了比較。
在我與Martin Fowler曾經(jīng)合作呈現(xiàn)的一次主題演講中,他作出了一個(gè)有洞察性的觀點(diǎn):
Java的遺產(chǎn)將是平臺(tái),而不是程序設(shè)計(jì)語言。
Java技術(shù)的原始工程師們作出了一個(gè)明智的決定,就是將編程語言與運(yùn)行時(shí)環(huán)境分開,最終這使得超過200種語言能夠運(yùn)行在Java平臺(tái)上。這種架構(gòu)對(duì)于該平臺(tái)的長期活力是至關(guān)重要的,因?yàn)橛?jì)算機(jī)程序設(shè)計(jì)語言的壽命一般都是比較短。從2008年開始,由Oracle主辦一年一度的JVM語言峰會(huì)為JVM上其它的語言實(shí)現(xiàn)與Java平臺(tái)工程師進(jìn)行開放式合作提供了機(jī)遇。
歡迎來到Java.next專欄系列,在本系列的文章中,我將講述三種現(xiàn)代JVM語言--Groovy,Scala和Clojure--它提供了范式,設(shè)計(jì)選擇與舒適因子之間一種有趣的混合。在此我不會(huì)花時(shí)間去深入介紹每種語言;在它們各自的站點(diǎn)上都有這類深度介紹。但這些語言社區(qū)的站點(diǎn)--它們主要目的是為了傳布這些語言--都缺乏客觀的信息,或者是該語言不適用的例子。在本系列的文章中我將進(jìn)行獨(dú)立地比較,以填補(bǔ)上述空白。這些文章將概述Java.next程序設(shè)計(jì)語言,以及學(xué)習(xí)它們的好處。
超越Java
Java程序設(shè)計(jì)語言達(dá)到卓越的程度就是,按Bruce Tate在他的Beyond Java一書中的說法,***風(fēng)暴:Web應(yīng)用的興起,已有Web技術(shù)由于種種原因不能適應(yīng)需求,企業(yè)級(jí)多層應(yīng)用開發(fā)的興起,這些因素共同造就了Java的卓越。Tate也指出這場(chǎng)風(fēng)暴是一系列***的事件,曾經(jīng)沒有其它語言使用相同的途徑達(dá)到相同的卓越程序。
Java語言已經(jīng)證明其在功能方面的強(qiáng)大靈活性,但它的語法與固有范式則存在著長期已知的局限性。盡管一些承諾過的變化即將引入到該語言中,但Java語法卻不能很容易地支持一些重要的未來語法特性,例如函數(shù)式編程中的某些特性。但如果你試圖去找到一種語言去替代Java,那么你就找錯(cuò)了。
多語言編程
多語言編程--在2006年的一篇博客中我使這個(gè)術(shù)語重?zé)ɑ盍Σ⒅匦铝餍衅饋?-是基于這樣的一種認(rèn)識(shí):沒有一種編程語言能夠解決每個(gè)問題。有些語言擁有某些內(nèi)建的特性,使其能夠更好地適應(yīng)特定的問題。例如,由于Swing十分復(fù)雜,開發(fā)者們發(fā)現(xiàn)很難編寫Java中的Swing UI,因?yàn)樗笫孪嚷暶黝愋?,為UI動(dòng)作定義煩人的匿名內(nèi)部類,還有其它的麻煩事兒。使用更適合構(gòu)建UI的語言,如Groovy中的SwingBuilder工具,去構(gòu)建Swing應(yīng)用會(huì)美妙得多。
運(yùn)行在JVM上的程序設(shè)計(jì)語言大量增多,這大大激發(fā)了多語言編程理念,因?yàn)槟憧梢曰煊镁幪?hào)語言,并可使用***匹配的語言,但同時(shí)卻維護(hù)著相同的底層字節(jié)碼和類庫。例如,SwingBuilder并不是要替代Swing;它只是搭建在已有的Swing API之上。當(dāng)然,在相當(dāng)長的時(shí)間內(nèi),開發(fā)者們還是將在JVM之外混合使用編程語言--例如,為特定目的而使用SQL和JavaScript--但在JVM的世界內(nèi),混合編程將變得更為流行。ThoughtWorks中的許多項(xiàng)目就合用著多種編程語言,而所有由ThoughtWorks Studios開發(fā)的工具則都要使用混合語言。
即便Java仍是你主要的開發(fā)語言,學(xué)習(xí)一下其它語言是如何工作的會(huì)讓你將它們納入你的未來戰(zhàn)略中。Java仍將是JVM生態(tài)系統(tǒng)中的重要組成部分,但最終它更多是作為該平臺(tái)的匯編語言--或是由于純粹的性能原因,或是在應(yīng)對(duì)特殊需求時(shí)才會(huì)用到它。
編程語言的進(jìn)化
當(dāng)上世紀(jì)八十年代我還在大學(xué)時(shí),我們使用著一種稱作Pecan Pascal的開發(fā)環(huán)境。它***的特性就是能使相同的Pascal代碼既可運(yùn)行在Apple II上,又可以運(yùn)行在IBM PC上。Pecan的工程師們?yōu)榱藢?shí)現(xiàn)這一目的使用了一種稱作"字節(jié)碼"的神秘之物。開發(fā)者們將他們的Pascal代碼編譯成"字節(jié)碼",該"字節(jié)碼"則運(yùn)行在為各個(gè)平臺(tái)編寫的原生"虛擬機(jī)"上。那是一段可怕的經(jīng)歷!最終程序慢的出奇,即便只是一個(gè)簡單的類賦值。當(dāng)時(shí)的硬件無法應(yīng)對(duì)這一挑戰(zhàn)。
Pecan Pascal之后的十年,Sun發(fā)布了使用相同架構(gòu)的Java,它受限也受利于上世紀(jì)九十年代的硬件環(huán)境。Java還加入了其它的對(duì)開發(fā)者友好的特性,如自動(dòng)的垃圾收集。由于曾經(jīng)使用過像C++之樣的語言,現(xiàn)在我再也不想使用沒有垃圾收集功能的語言去編碼了。我寧愿花時(shí)間在更高抽象層次上去思考復(fù)雜的業(yè)務(wù)問題,而不是像內(nèi)存管理這樣的復(fù)雜管道問題。
計(jì)算機(jī)語言通常沒有很長壽命的原因之一就是語言和平臺(tái)設(shè)計(jì)的創(chuàng)新速度。由于我們的平臺(tái)變得更為強(qiáng)大,它們可以處理更多的額外工作。例如,Groovy的內(nèi)存化(memoization)特性(2010年加入)會(huì)緩存函數(shù)調(diào)用的結(jié)果。不需要手工編寫緩存代碼,那樣會(huì)引入潛在的缺陷,你僅僅只是需要調(diào)用memoize方法而以,如清單1所示:
清單1. 在Groovy中內(nèi)存化函數(shù)
- def static sum = { number ->
- factorsOf(number).inject(0, {i, j -> i + j})
- }
- def static sumOfFactors = sum.memoize()
在清單1中,sumOfFactors方法返回的結(jié)果會(huì)被自動(dòng)緩存。你還可以使用方法memoizeAtLeast()和memoizeAtMost()去定制緩存行為。Clojure也含有內(nèi)存化特性,在Scala中也有略有實(shí)現(xiàn)。像內(nèi)存化這樣存在于下一代編程語言(以及某些Java框架)中的高級(jí)特性也將逐漸地進(jìn)入到Java語言中。Java的下一個(gè)版本中將加入高階函數(shù)(higher-order function),這使得內(nèi)存化更容易被實(shí)現(xiàn)。通過研究下一代Java語言,你就可以先睹Java的未來特性為快了。
Groovy,Scala和Clojure
Groovy是二十一世紀(jì)的Java語法--濃縮咖啡取代了傳統(tǒng)咖啡。Groovy的設(shè)計(jì)目標(biāo)是更新并消除Java語法中的障礙,同時(shí)還要支持Java語言中的主要編程范式。因此,Groovy要"知曉"諸如JavaBean,它會(huì)簡化對(duì)屬性的訪問。Groovy會(huì)以很快的速度納入新特性,包括函數(shù)式編程中的重要特性,這些特性我將在本系列的后續(xù)篇章中著重描述。Groovy仍然主要是面向?qū)ο蟮拿钍秸Z言。Groovy區(qū)別于Java的兩個(gè)基本不同點(diǎn):它是動(dòng)態(tài)而非靜態(tài)的;它是的元編程能力要好得多。
Scala從骨子里就是為了利用JVM而進(jìn)行設(shè)計(jì)的,但是它的語法則是完全被重新設(shè)計(jì)過了。Scala是強(qiáng)靜態(tài)類型語言--它的類型要求比Java還嚴(yán)格,但造成的麻煩卻很少--它支持面向?qū)ο蠛秃瘮?shù)式范式,但更偏好于后者。例如,Scala更喜歡val聲明,這會(huì)生成不可變變量(類似于在Java中將變量聲明為final)賦給var,而var將創(chuàng)建更為大家所熟悉的可變變量。通過對(duì)這兩種范式的深度支持,Scala為你可能想要的(面向?qū)ο蟮拿钍骄幊?與你所應(yīng)該想要的(函數(shù)式編程)之間架起了一座橋梁。
Clojure是最激進(jìn)的,它的語法是從其它語言中分離出來,被認(rèn)為是Lisp的方言。Clojure是強(qiáng)動(dòng)態(tài)類型語言(就像Groovy),它反映了一種義無反顧的設(shè)計(jì)決策。雖然Clojure允許你與遺留的Java程序進(jìn)行全面而深度的交互,但是它并不試圖構(gòu)建一座橋梁去連接面向?qū)ο蠓妒?。例如,Clojure是函數(shù)式編程的鐵桿,也支持面向?qū)ο笠栽试S與該種范式進(jìn)行互操作。盡管它支持面對(duì)對(duì)象程序員所習(xí)慣的全部特性,如多態(tài)--但,是以函數(shù)式風(fēng)格,而非面向?qū)ο箫L(fēng)格進(jìn)行實(shí)現(xiàn)的。設(shè)計(jì)Clojure時(shí)遵循了一組核心的工程原則,如軟件事務(wù)內(nèi)存(Software Transactional Memory),這是為了迎合新功能而打破了舊有的編程范式。
編程范式
除語法之外,這些語言之間的最有趣的不同之處就是類型及其內(nèi)在的編程范式:函數(shù)式或命令式。
靜態(tài)類型 vs. 動(dòng)態(tài)類型
編程語言中的靜態(tài)類型要求顯式的類型聲明,例如Java中的int x;聲明語句。動(dòng)態(tài)類型語言并不要求在聲明時(shí)提供類型信息。此處所考慮的語言都是強(qiáng)類型語言,意即程序在賦值之后能夠反射出類型。
Java的類型系統(tǒng)廣受詬病之處就是其靜態(tài)類型有太多不便,且又沒有提供足夠的益處。例如,在當(dāng)前的有限的類型推導(dǎo)出現(xiàn)之前,Java要求開發(fā)者在賦值語句兩邊要重復(fù)地聲明類型。Scala的類型比Java的更為靜態(tài),但在日常使用中所遇到的不便要少得多,因?yàn)樗罅渴褂昧祟愋屯茖?dǎo)。
初看Groovy,它似乎有一種行為能夠銜接靜態(tài)與動(dòng)態(tài)之間的隔閡??紤]如清單2所示的簡單對(duì)象集合工廠:
清單2. Groovy集合工廠
- class CollectionFactory {
- def List getCollection(description) {
- if (description == "Array-like")
- new ArrayList()
- else if (description == "Stack-like")
- new Stack()
- }
- }
清單2中的類表現(xiàn)為一個(gè)工廠類,基于傳入的description參數(shù),該工廠返回List接口的兩種實(shí)現(xiàn)--ArrayList或Stack--之一。對(duì)于Java開發(fā)者,上述代碼確保了返回值能夠符合約定。然后,清單3中的兩個(gè)單元測(cè)試揭示了一種復(fù)雜性:
清單3. Groovy中的集合類型測(cè)試
- @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中的***個(gè)單元測(cè)試中,使用前述的工廠類獲得一個(gè)Stack對(duì)象,并驗(yàn)證它是否確實(shí)是Stack對(duì)象,然后再執(zhí)行棧操作,例如push(),size()和search()。然而,在第二個(gè)單元測(cè)試中,我必須聲明一個(gè)期望的異常MissingMethodException才能確保該測(cè)試能夠通過。當(dāng)我獲取一個(gè)Array-like的集合,并將它賦給List類型的變量時(shí),我能夠驗(yàn)證返回的類型確為一個(gè)List對(duì)象。但是,當(dāng)我試圖調(diào)用search()方法時(shí)將觸發(fā)異常,因?yàn)锳rrayList并不包含search()方法。因此,這種聲明無法在編譯時(shí)確保方法的調(diào)用是正確的。
雖然這看起來像是一個(gè)缺陷,但這種行為卻是恰當(dāng)?shù)?。Groovy中的類型只是確保賦值語句的有效性。例如,在清單3中,如果返回的實(shí)例未實(shí)現(xiàn)List接口,將會(huì)觸發(fā)一個(gè)運(yùn)行時(shí)異常GroovyCastException。鑒于此,可以肯定Groovy能夠與Clojure同躋身于強(qiáng)動(dòng)態(tài)類型語言家族。
然而,Groovy***的一些變化使得它的靜態(tài)與動(dòng)態(tài)之間的隔閡變得掃地清。Groovy 2.0加入了注解@TypeChecked,該注解可使你特別地對(duì)類或方法決定進(jìn)行嚴(yán)格的類型檢查。清單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,它同時(shí)對(duì)賦值及隨后的方法調(diào)用進(jìn)行了驗(yàn)證。例如,清單5中的代碼將不能通過編譯:
清單5. 防止無效方法調(diào)用的類型檢查
- @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中,我必須對(duì)集合工廠返回的對(duì)象進(jìn)行強(qiáng)制類型轉(zhuǎn)換,這樣才能允許我調(diào)用Stack類中的search()方法。但這種方式會(huì)產(chǎn)生一些局限性:當(dāng)使類型靜態(tài)化之后,Groovy的很多動(dòng)態(tài)特性將無法工作。然而,上述救命證明了Groovy將繼續(xù)進(jìn)行改進(jìn),以彌合靜態(tài)性與動(dòng)態(tài)性之間的分歧。
所有這些語言都有十分強(qiáng)大的元編程功能,所以更為嚴(yán)苛的類型化可以在事后再添加進(jìn)來。例如,已有多個(gè)分支項(xiàng)目將選擇性類型(selective type)引入到Clojure中。但一般認(rèn)為選擇性類型是可選的,它不是類型系統(tǒng)的一部分;它只是一個(gè)類型驗(yàn)證系統(tǒng)。
命令式 vs. 函數(shù)式
另一個(gè)主要的比較維度就是命令式與函數(shù)式。命令式編程注重于單步執(zhí)行的結(jié)構(gòu),在許多情況下,它是模仿了早期底層硬件的有益結(jié)構(gòu)。函數(shù)式編程則注重將函數(shù)作為***等的結(jié)構(gòu)體,以試圖將狀態(tài)傳遞與可變性降低到最小。
Groovy在很大程度上是受Java的啟發(fā),它在根本上仍然是命令式語言。但從一開始,Groovy就加入了許多函數(shù)式命令的特性,并且以后還會(huì)加入更多的此類特性。
Scala則彌合了這兩種編程范式,它同時(shí)支持這兩種范式。在更偏向(也更鼓勵(lì))函數(shù)式編程的同時(shí),Scala依然支持面向?qū)ο蠛兔钍骄幊獭R虼?,為了恰?dāng)?shù)厥褂肧cala,就要求團(tuán)隊(duì)要受到良好的培訓(xùn),以確保你不會(huì)混用和隨意地選擇編程范式,在多范式編程語言中,這一直都是一個(gè)危險(xiǎn)。
Clojure是鐵桿的函數(shù)式編程語言。它也支持面向?qū)ο筇匦?,使得它能夠很容易地與其它JVM語言進(jìn)行交互,它并不試圖去彌合這兩種范式之間的隔閡。相反,Clojure這種義無反顧的決策使它的設(shè)計(jì)者所考慮的語句成為很好的工程學(xué)實(shí)踐。這些決策具有深遠(yuǎn)的影響,它使Clojure能夠以開創(chuàng)性的方法去解決Java世界中一些揮之不去的問題(如并發(fā))。
在學(xué)習(xí)這些新語言時(shí)所要求的許多思想上的轉(zhuǎn)變就是源自于命令式與函數(shù)式之間的巨大差別,而這也正是本系列文章所要探索的最有價(jià)值的領(lǐng)域之一。
結(jié)論
開發(fā)者們正生活在一個(gè)多語言編程快速發(fā)展的世界中,在這種環(huán)境中,要求使用多種不同的語言去解決問題。學(xué)習(xí)高效地利用新語言可以幫助你決定哪種方法是合適的。即便你無法離開Java,它也會(huì)逐步地將下一代JVM語言中的特性納入到Java中;現(xiàn)在看看這些新特性,就會(huì)使你在潛移默化之中掌握到未來的Java語言。
在本系列的下一篇文章中,我將開始通過探索Groovy,Scala和Clojure中的共通之處來對(duì)它們進(jìn)行比較。
原文鏈接:http://www.blogjava.net/jiangshachina/archive/2013/02/06/395164.html