Groovy 2.0新特性
http://www.infoq.com/cn/articles/new-groovy-20/
Groovy 2.0致力于三個關鍵主題:
更高性能:借助JDK 7 Invoke Dynamic的支持,它會為那些有幸在生產環境中已使用JDK7的開發者提高Groovy的速度;對于使用JDK 5及以上版本的所有人,則要借助靜態編譯,特別是那些打算放棄一些動態特性避免"猴子補丁"并想獲得與Java相同速度的人而言。
對于Java更友好:Java 7 Project Coin增強的支持讓Groovy和Java一如既往的表現為親密的語法表兄弟,并且在靜態類型檢查器上給予將Groovy作為Java腳本語言的開發者跟javac編譯器相同的反饋和類型安全。
模塊化更佳:借助新的模塊化級別,Groovy開啟了更小交付包的大門,例如,集成進Android上的移動應用,并允許Groovy API發展和融入新的版本和新的擴展模塊,同時還允許用戶為現有類型貢獻新的方法。
新發布的Groovy2.0為這門語言帶來了關鍵的靜態特性:靜態類型檢查和靜態編譯;采用了JDK 7相關的改進:Project Coin語法增強和新支持的“invoke dynamic” JVM指令;同時,提高了模塊化。我們將在這篇文章中了解這些新特性的詳情。
面向動態語言的“靜態主題”
靜態類型檢查
Groovy天生而且永遠都是動態語言。但Groovy也常被當作"Java腳本語言",或是“更好的Java”(即限制更少且功能更強的Java)。實際上,許多Java開發者將Groovy作為一種擴展語言使用或嵌入到自己的Java應用中,如編寫表現力更強的業務規則、為不同客戶進一步定制應用等。對于這種面向Java的使用場景,開發者并不需要這門語言提供的所有動態能力,他們通常期望能從Groovy編譯器得到跟javac編譯器一樣的反饋。特別是,他們希望得到編譯錯誤(而非運行時錯誤),如變量或方法名的拼寫錯誤、錯誤的類型賦值等。這就是Groovy 2支持靜態類型檢查的原因。
指出明顯的拼寫錯誤
靜態類型檢查器建立在Groovy已有、強大的AST(抽象語法樹)之上,不熟悉它們的開發者可以將其視為一種利用注解觸發的可選編譯器插件。作為可選特性,不需要它時,Groovy不會強制你使用。要觸發靜態類型檢查,只需在方法或類上使用@TypeChecked
注解就可以在你期望的粒度級別打開檢查。讓我們首先看一個示例:
import groovy.transform.TypeCheckedvoid someMethod() {}@TypeCheckedvoid test() { // 編譯錯誤: // 找不到匹配的sommeeMethod() sommeeMethod() def name = "Marion" // 編譯錯誤: // 沒有聲明變量naaammme println naaammme}
我們用@TypeChecked
注解了test()
方法,它告訴Groovy編譯器在編譯時對指定的方法進行靜態類型檢查。我們試圖調用帶有明顯拼寫錯誤的someMethod()
,并打印另一個拼錯的name變量,編譯器會分別拋出2個編譯錯誤,因為找不到對應的方法和變量聲明。
檢查賦值和返回值
靜態類型檢查器還會驗證返回類型和賦值是否一致:
相關廠商內容
關于紅包、SSD云盤等核心技術集錦!
跟技術大牛,侃侃容器那些事兒!
付錢拉開發者支持大禮包速領!!
阿里百川熱修復技術Hotfix公測開啟
【雙11】不犯同樣錯誤,總結蘇寧易購核心交易線近十年演變的經驗教訓
import groovy.transform.TypeChecked@TypeCheckedDate test() { // 編譯錯誤: // 不能把Date賦給 * // int類型的變量 int object = new Date() String[] letters = ['a', 'b', 'c'] // 編譯錯誤: // 不能把String類型的值賦給 * // Date類型的變量 Date aDateVariable = letters[0] // 編譯錯誤: // 無法在返回Date類型的方法中 * // 返回String類型的值 return "today"}
在這個示例中,編譯器會抱怨這樣的事實:你沒法把Date
賦給int
變量,也沒法返回String
來取代方法簽名中指定的Date
。正中間腳本引起的編譯錯誤也很有趣,因為它不僅抱怨了錯誤的賦值,而且還因為它展示了動態類型推斷的能力,這當然是由于類型檢查器知道letters[0]
是String
類型,因為我們正在處理一個String
數組。
類型推斷的更多細節
既然談到了類型推斷,那我們就看看它的一些其他表現形式。我們曾說過類型檢查器會跟蹤返回類型和值:
import groovy.transform.TypeChecked@TypeCheckedint* method() { if (true) { // 編譯錯誤: // 無法在返回int類型的方法中 // 返回String類型的值 'String' } else { 42 }}
若方法返回原始類型的int
值,類型檢查器還能夠檢查出不同結構的返回值,如if/else
分支、try/catch
塊或switch/case
塊。在該示例中,if/else
塊的一個分支試圖返回一個String
值而非原始類型的int
,這時編譯器就開始抱怨了。
常見的類型轉換依然可用
但靜態類型檢查器不會對Groovy支持的某些自動類型轉換進行抱怨。例如,對于返回String、boolean
或Class
的方法簽名,Groovy會自動將返回值轉換到這些類型:
import groovy.transform.TypeChecked@TypeCheckedboolean booleanMethod() { "non empty strings are evaluated to true"}assert booleanMethod() == true@TypeCheckedString stringMethod() { // 調用toString()將StringBuilder轉換成String new StringBuilder() << "non empty string"}assert stringMethod() instanceof String@TypeCheckedClass classMethod() { // 會返回java.util.List類 "java.util.List"}assert classMethod() == List
靜態類型檢查器的智能足以完成類型推斷:
import groovy.transform.TypeChecked@TypeCheckedvoid method() { def name = " Guillaume " // 判斷出是String類型(就算它是在GString中) println "NAME = ${name.toUpperCase()}" // 支持Groovy GDK方法 // (也支持GDK操作符重載) println name.trim() int[] numbers = [1, 2, 3] // 元素n是int for (int n in numbers) { println }}
盡管name
變量是用def
定義的,但類型檢查器還是知道它是String
類型。接下來,當這個變量被插入用在string中時,它知道name變量能調用String的toUpperCase()
方法,或者之后的trim()
方法,該方法是由Groovy Development Kit添加用來裝飾String
類的。最后,當循環原始的int
數組時,它還知道數組中的元素明顯就是int
。
動態特性和靜態化類型方法的混合使用
記住一點很重要:使用靜態類型檢查工具會限制你能在Groovy中使用的特性。大多數運行時動態特性是不允許的,因為它們沒法在編譯時被靜態類型檢查。因此,通過類型的元類(metaclass)在運行時添加一個新方法是不允許的。但是,當你需要使用一些特殊的動態特性時,比如Groovy的構建器(builder),只要愿意,你可以選擇不使用靜態類型檢查。
@TypeChecked
注解可用于類或方法級別。因此,要是想對整個類進行類型檢查,就把它用在類上,若只想對某些方法進行類型檢查,可以把它用在那些方法上。此外,若想對所有內容進行類型檢查,但排除某個特殊方法,你可以對被排除方法使用@TypeChecked(TypeCheckingMode.SKIP)
- 或簡化版本@TypeChecked(SKIP)
,前提是你靜態導入了相關枚舉。以下腳本說明了這種情況,greeting()
方法需要類型檢查,而generateMarkup()
方法不需要:
import groovy.transform.TypeCheckedimport groovy.xml.MarkupBuilder// 這個方法和它的代碼要進行類型檢查@TypeCheckedString greeting(String name) { generateMarkup(name.toUpperCase())}// 這個方法不需要類型檢查// 并且你可以使用像markup builder這樣的動態特性String generateMarkup(String name) { def sw =new StringWriter() new MarkupBuilder(sw).html { body { div name } } sw.toString()}assert greeting("Cédric").contains("<div>CéDRIC</div>")
類型推斷和instanceof檢查
當前的Java發行版不支持通用的類型推斷;因此今天我們發現很多地方的代碼往往相當冗長并且結構混亂。這掩蓋了代碼的意圖,而且沒有強大的IDE支持也很難寫代碼。這是instanceof
檢查的應用場景:你經常會在if
條件中使用instanceof檢查值的類,并且在if
塊之后,你必須使用對象轉型(cast)才能使用這個對象值的方法。用一般的Groovy代碼,結合新的靜態類型檢查模式,你可以徹底擺脫那些對象轉型。
import groovy.transform.TypeCheckedimport groovy.xml.MarkupBuilder@TypeCheckedString test(Object val) { if (val instanceof String) { // 不同于Java的寫法: // return ((String)val).toUpperCase() val.toUpperCase() } else if (val instanceof Number) { // 不同于Java的寫法: // return ((Number)val).intValue().multiply(2) val.intValue() * 2 }}assert test('abc') == 'ABC'assert test(123) == '246'
在上面的示例中,靜態類型檢查器知道參數val在if
塊中是String
類型,在else if塊中是 Number
,無需任何轉換。
最低上限
靜態類型檢查器在類型推斷方面走得更遠,從某種意義上講它對你的對象類型了解更精細。考慮下面的代碼:
import groovy.transform.TypeChecked// 推斷返回類型:// 一個可比較和可序列化的數字列表@TypeChecked test() { // 一個整型和一個BigDecimal return [1234, 3.14]}
在這個示例中,憑直覺,我們返回了一組數字:一個Integer
和一個BigDecimal
。但是靜態類型檢查器計算了我們所說的“最低上限(lowest upper bound)”,它實際上是一個數字列表,而且是可序列化和可比較的。用標準Java類型符號不可能表示該類型,但如果我們有一些類似與操作(&)的交集操作符,它看起來就像List<Number & Serializable & Comparable>。
流式轉型(Flow typing)
盡管其實不應該將這種做法視為好實踐,但有時開發者會使用相同的無類型變量來存儲不同類型的值。看看方法體:
import groovy.transform.TypeChecked@TypeChecked test() { def var = 123 // 推斷出的類型是int var = "123" // 用一個String給var賦值 println var.toInteger() // 沒問題,不需要轉型 var = 123 println var.toUpperCase() // 出錯了,var是int型!}
var
變量一開始被初始化為int
。然后,被賦給一個String
。"流式轉型(flow typing)"算法根據賦值流程知道變量現在持有一個String
,所以靜態類型檢查器會樂于接受由Goovy添加到String
上的toInteger()
方法。接下來,一個數字被放回到var變量中,但是緊接著調用toUpperCase()
時,類型檢查器將拋出一個編譯錯誤,因為Integer
上沒有toUpperCase()
方法。
對于被共享給對其感興趣的閉包中的變量,流式轉型算法有些特殊的情況。當局部變量被定義該變量的方法中的閉包引用時,會發生什么?看看這個示例:
import groovy.transform.TypeChecked@TypeChecked test() { def var = "abc" def cl = { if (new Random().nextBoolean()) var = new Date() } cl() var.toUpperCase() // 編譯錯誤!}
局部變量var
被賦值為String
,但接著,若某個隨機值為真,var
可能會被賦值為Date
。一般情況下,只有在運行時我們才確切知道閉包的if語句中的條件為真還是假。因此,編譯器不可能在編譯時知道var
現在是String
還是Date
。這就是編譯器對于toUpperCase()
調用抱怨的原因,因為它無法推斷變量包含的是String
。這個例子雖略顯做作,但是下面有一些有趣的例子:
import groovy.transform.TypeCheckedclass A { void foo() {} }class B extends A { void bar() {} }@TypeChecked test() { def var = new A() def cl = { var = new B() } cl() // var起碼是個A的實例 // 所以我們允許調用foo()方法 * var.foo()}
在上面的test()
方法中,var
被賦予A
的一個實例,然后在閉包中被賦予B
的一個實例,因此我們至少可推斷出var類型A
。
所有這些添加到Groovy編譯器中的檢查都是在編譯時完成的,但是生成的字節碼像往常一樣仍是相同的動態碼 - 在行為上根本沒變。
由于編譯器現在知道你程序中類型方面的很多事情,它向許多有趣的能力敞開了大門:靜態編譯那些被類型檢查的代碼怎樣?除了其他優勢,一個明顯優勢是生成的字節碼將更接近于由javac編譯器自己生成的字節碼,讓靜態編譯過的Groovy代碼跟純Java代碼一樣快。在下一節,我們將了解更多關于Groovy靜態編譯的內容。
靜態編譯
正如我們將在以下關于向JDK 7靠齊的章節中看到的,Groovy 2.0支持JVM新的"invoke dynamic"指令及其相關API,它們簡化了Java平臺上動態語言的開發并為Groovy的動態調用帶來了額外的性能提高。可不幸的是,在本文撰寫時,JDK 7尚未被部署于生產環境,因而并非所有人都有機會運行最新版本。所以期待性能改進的開發者若沒法運行在JDK 7上,就不會在Groovy 2.0中看到太多的改變。所幸,Groovy開發團隊考慮到了這些開發者(除了其他改進之外)會對性能改進感興趣,其手段就是允許類型檢查后的代碼代碼可被靜態編譯。
廢話少說,讓我們現在就親手試試新的@CompileStatic
注解:
import groovy.transform.CompileStatic@CompileStaticint* squarePlusOne(int num) { num * num + 1}assert squarePlusOne(3) == 10
這次使用的是@CompileStatic
,而非@TypeChecked
,并且你的代碼會被靜態編譯,同時生成的字節碼非常像javac的字節碼,運行速度一樣。就像@TypeChecked
注解,@CompileStatic
能注解類和方法,@CompileStatic(SKIP)
可以讓某個方法在其所屬類被@CompileStatic
標記時不被靜態編譯。
生成類javac(javac-like)字節碼的另一好處是那些被注解的方法的字節碼大小會比通常Groovy為動態方法生成的字節碼的大小要小,因為要支持Groovy的動態特性,動態場景下的字節碼包含了調用Groovy運行時系統的額外指令。
最后一點值得注意的是,框架或庫代碼作者可使用靜態編譯,這有助于避免當代碼庫中多個部分使用動態元編程時的負面影響。像Groovy這類語言中可用的動態特性給開發者帶來了極強的能力和靈活性,但鑒于元編程特性是動態發揮作用的,若不加注意,不同的假設會存在于系統的不同部分,由此產生意想不到的后果。舉一個例子(雖然有點刻意為之),假設你在使用兩個不同的庫時發生的情景,兩個庫都給你的核心類添加了一個名字相似但實現不同的方法。什么行為是期望的?有經驗的動態語言使用者可能之前就見過這個問題,并且可能聽說它被稱為“猴子補丁(monkey patching,譯注:在不改變原始代碼的情況下擴展或修改動態語言運行時代碼的方法)”。若能靜態編譯代碼庫中的部分代碼 - 那些不需要動態特性的代碼 - 保護了你不受猴子補丁的影響,因為靜態編譯后的代碼不會經過Groovy的動態運行系統。盡管語言的動態運行時方面不允許出現在靜態編譯環境中,但所有常用的AST轉換機制還會像以前一樣工作良好,因為多數AST轉換機制也是在編譯時施展它們的魔法。
說到性能,Groovy的靜態編譯代碼通常會或多或少跟javac的一樣快。在開發團隊使用的一些微基準測試中,有些情況下性能相同,而有時則可能稍慢。
在以前,由于Java和Groovy透明無縫的集成,我們過去常建議開發者優化Java的hotspot例程以獲得進一步改進性能,但是現在,有了這個靜態編譯選擇,情況變了,那些想完全用Groovy開發項目的人們也能這樣做了。
Java 7和JDK 7主題
Groovy編程語言的語法其實來自于Java語法本身,但很明顯,Groovy提供了額外漂亮的便捷方法讓開發者生產力更高。讓Java開發者熟悉的語法一直以來都是這個項目的重要賣點,并且被廣泛接納,這得益于平坦的學習曲線。我們當然也期望Groovy用戶和新人也能從Java 7增加的"Project Coin"所提供的一些語法改進中受益。
除了語法,JDK 7還為它的API帶來了一些有趣的新事物,這是長久以來的第一次,它甚至添加了一個被稱為"invoke dynamic"的字節碼指令,它旨在讓實現者更容易地開發他們的動態語言和獲得更高的性能。
Project Coin語法增強
從第一天開始(這要從2003年說起!),Groovy就擁有幾處建立在Java之上的語法增強和特性。例如,人們可以想到的是閉包,以及switch/case
語句中可使用的不僅限于離散值,而Java 7中只是多了能使用多個String
。所以一些Project Coin語法增強,比如switch中的多個String
,已經在Groovy中了。然而,有些增強是新的,如二進制字面量、數字字面量中的下劃線或者多catch塊,Groovy 2都支持。唯一漏掉的Project Coin增強是"try with resources"結構,對于它,Groovy通過Groovy Development Kit豐富的API提供了多個替代解決方案。
二進制字面量
在Java 6及之前版本,以及Groovy中,數字可以表示成十進制、八進制和十六進制,而在Java 7和Groovy 2中,你可以使用以“0b”做前綴的二進制符號:
int x = 0b10101111assert x == 175byte aByte = 0b00100001assert aByte == 33int anInt = 0b1010000101000101assert anInt == 41285
數字字面量中的下劃線
當寫長變量數字時,很難用肉眼分辨出一些數字是如何分組聚合在一起的,例如千位分組,單詞等等。通過允許在數字字面量中放置下劃線,就很容易區分這些分組了:
long creditCardNumber = 1234_5678_9012_3456Llong socialSecurityNumbers = 999_99_9999Ldouble monetaryAmount = 12_345_132.12long hexBytes = 0xFF_EC_DE_5Elong hexWords = 0xFFEC_DE5Elong maxLong = 0x7fff_ffff_ffff_ffffLlong alsoMaxLong = 9_223_372_036_854_775_807Llong bytes = 0b11010010_01101001_10010100_10010010
多catch塊
當捕獲到異常時,我們通常會復制兩個或更多的異常塊,因為我們想用同樣的方式處理它們。解決方法是,要么在它自己的方法中分離出通用的內容,或者一種更丑陋的方式就是通過捕獲Exception
(或者更糟的Throwable
)完成一個捕獲所有異常的方法。用多catch塊,我們能定義要用一個catch塊捕獲和處理的多種異常:
try { / ... /} catch(IOException | NullPointerException e) { / 一個代碼塊處理2個異常 /}
Invoke Dynamic的支持
正如本文之前提到的,JDK 7帶來了一個被稱為"invoke dynamic"的新字節碼指令以及相關的API。其目的是幫助動態語言實現者在Java平臺之上打造自己的語言,實現手段則是:簡化動態方法的調用路徑,定義可緩存動態方法的"call site",作為方法指針的"method handles",存儲類對象中各種元數據的"class values",以及其他一些內容。不過事先提醒,盡管承諾性能改進,但"invoke dynamic"在JVM內部還沒有完全優化,也并不總能提供最好的性能,但隨著一步步的更新,優化就會到來。
Groovy帶來了它自己的實現技術,用“call site緩存”加速方法的選擇和調用,用元類注冊庫存儲元類(類的等價動態運行時),執行跟Java一樣快的原生原始計算(native primitive calculation),等等。但隨著“invoke dynamic”的問世,我們將重新把Groovy的實現置于這些API和JVM字節碼指令之上,以獲得性能的改進和簡化我們的代碼庫。
如果有幸運行JDK 7,你就能使用已經編譯進"invoke dynamic"支持的Groovy JAR的新版本。很容易辨認那些JAR,因為它們名字都含有"-indy"區分。
啟用invoke dynamic支持
然而,要想利用"invoke dynamic",光用"indy"JAR編譯你的Groovy代碼還不夠。鑒于此,使用“groovyc”編譯器或者“groovy”命令時,你必須使用--indy標記。這也就意味著,就算用的是indy JAR,你仍可以面向JDK 5或6進行編譯。
同樣的,如果你正在使用groovyc Ant task編譯你的項目,你還可以指定indy屬性:
...<taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc" classpathref="cp"/>...<groovyc srcdir="${srcDir}" destdir="${destDir}" indy="true"> <classpath>... </classpath></groovyc>...
Groovy Eclipse Maven編譯器插件還沒有更新包含Groovy 2.0支持,但也快了。對于GMaven插件的用戶,盡管已經可以配置插件使用Groovy 2.0,目前還沒有支持invoke dynamic的標志位。同樣,GMaven很快也會有這方面的更新。
當在Java應用中集成Groovy時,用GroovyShell
,你還可以通過給GroovyShell
構造函數傳遞一個CompilerConfiguration
實例來激活invoke dynamic支持,在GroovyShell
上可以訪問和設置優化選項:
CompilerConfiguration config = new CompilerConfiguration();config.getOptimizationOptions().put("indy", true);****config.getOptimizationOptions().put("int", false);GroovyShell shell = new GroovyShell(config);
由于invokedynamic被期望成能夠完全替代動態方法分發,禁用那些為了優化邊緣情況而生成額外二進制碼的原始優化(primitive optimizations)是有必要的。即使在某些情況下它比激活原始優化慢,JVM的未來版本將會對JIT有所改進,它將有能力內聯(inlining)多數調用并去除那些沒必要的裝箱(boxing)。
性能改進承諾
在我們的測試中,我們注意到有些領域取得了有趣的性能改進,而其他程序比沒使用invoke dynamic支持的運行慢。然而,Groovy團隊在Groovy 2.1的pipeline中取得了進一步的性能改進,但我們注意到JVM還沒有微調,全面優化仍然有很長的路要走。但所幸,即將到來的JDK 7的更新(尤其是更新8)應該已經包含了這樣的改進,這樣的情況必將改善。此外,隨著invoke dynamic被用于JDK 8的 Lambdas實現,我們可以保證未來會有更大的改進。
模塊性更佳的Groovy
我們將通過模塊化介紹,結束這次Groovy 2.0新特性之旅。就像Java,Groovy不只是一門語言,它還是服務于多種用途的API集合:模板、Swing UI構建、Ant腳本、JMX集成、SQL訪問、servlet服務等。Groovy的發布版就是把所有這些特性和API打成一個大的JAR。但不是所有人在自己的應用里總是需要所有內容:如果正在寫Web應用,你會對模板引擎和Servlet感興趣,但是如果正在做一個富桌面客戶端程序,你可能僅需要Swing構建器。
Groovy的模塊
因此,本版本模塊化的第一個目標就是將原始的Groovy JAR真真切切的劃分成更小的模塊、更小的JAR。核心的Groovy JAR文件現在縮小了一半,我們有如下可用的特性模塊:
Ant:為自動化管理任務提供腳本化的Ant任務;
BSF:用老的Apache Bean腳本框架為你的Java應用集成Groovy;
Console:包含Groovy Swing console的模塊;
GroovyDoc:文檔化你的Groovy和Java類;
Groovysh:與Groovysh命令行shell相關的模塊;
JMX:暴露和消費JMX bean;
JSON:生產和消費JSON
JSR-223:使用JDK 6+ javax.scripting API將Groovy集成到你的Java應用中
Servlet:編寫和服務Groovy script servlet和template
SQL:查詢關系數據庫;
Swing:構建Swing UI;
Templates:使用模板引擎
Test:某些測試支持,如GroovyTestCase、mocking等等;
TestNG:用Groovy寫TestNG測試;
XML:產生和消費XML文檔。
對于Groovy 2,你現在可以只挑選感興趣的模塊,而不用把所有內容都帶入到classpath中。但我們仍提供包含所有內容的“完整”JAR,假如你不想只是為了節省一點空間就要處理復雜的依賴關系的話。我們還為運行在JDK7上的代碼提供了用“invoke dynamic”支持選項編譯后的JAR文件。
擴展模塊
讓Groovy變得更模塊化的工作也產生了一個有趣的新特性:擴展模塊(extension module)。通過將Groovy分裂成更小模塊,方便模塊擴展方法的機制已經建立。由此,擴展模塊可以給其他類,包括來自JDK或第三方庫的類,提供實例和靜態方法。Groovy用這種機制修飾了來自JDK的類,給諸如String、File
、流以及其他更多的類添加了新的有用方法 - 例如,URL上的getText()
方法,允許你通過HTTP get獲得遠程URL的內容。還需要注意的是,靜態類型檢查器和編譯器也知道你模塊中的這些擴展方法。但先看看如何給現有類型添加新的方法。
添加實例方法
要給現有類型添加新的方法,你必須創建一個包含這些方法的幫助類。在這個幫助類中,所有的擴展方法其實都是public
的(這在Groovy中是缺省的,但若用Java實現,就需要標出)和static
的(盡管它們將在類的實例中可用)。它們接受的第一個參數其實總是要在上面調用擴展方法的實例。余下參數將在調用時被傳入。這跟Groovy的Category使用的是一樣的慣例。
假定我們要給String
添加一個greets()
方法, 它向作為參數傳入的人名問好,所以你可以像下面這樣寫:
assert "Guillaume".greets("Paul") == "Hi Paul, I'm Guillaume"
要實現它,你要創建一個含有這個擴展方法的幫助類,如:
package com.acmeclass MyExtension { static String greets(String self, String name) { "Hi ${name}, I'm ${self}" }}
添加靜態方法
對于靜態擴展方法,用同樣的機制和慣例。現在我們給Random添加一個靜態方法,獲得兩個值之間的一個隨機整數,你可以按照這個類來處理:
package com.acmeclass MyStaticExtension { static String between(Random selfType, int start, int end) { new Random().nextInt(end - start + 1) + start }}
這樣,你可以用如下方式使用這個擴展方法:
Random.between(3, 4)
擴展模塊描述符
一旦編寫好了包含擴展方法的幫助類(用Groovy或Java),你需要為模塊創建描述符。你必須在模塊文件夾的META-INF/services
目錄下創建一個名為org.codehaus.groovy.runtime.ExtensionModule
的文件。可以定義四個基本屬性,告訴Groovy運行時模塊的名字和版本,以及用逗號隔開的類名列表,這些類就是為擴展方法寫的幫助類。如下是我們最終的模塊描述:
moduleName = MyExtensionmoduleVersion = 1.0extensionClasses = com.acme.MyExtensionstaticExtensionClasses = com.acme.MyStaticExtension
一旦Classpath中有了這個擴展模塊描述符,現在就能在代碼中使用這些擴展方法了,不需要import或者其他動作,因為這些擴展方法是自動注冊的。
獲取擴展
在腳本中使用@Grab注解可以從類似Maven Central這樣的Maven庫中獲取依賴。此外,使用@GrabResolver注解,你還能為依賴指定自己的位置。如果你正通過這種機制“獲取”一個擴展模塊,擴展方法也會被自動安裝。理想情況下,出于一致性考慮,模塊名字和版本應該跟制品的id和版本關聯。
總結
Groovy在Java開發人員中很流行,并為他們的應用提供了成熟的平臺和生態系統。但我們并未滿足于現狀,Groovy開發團隊會一如既往繼續提高語言和它的API,幫助用戶在Java平臺上提高他們的生產率。
Groovy 2.0致力于三個關鍵主題:
更高性能:借助JDK 7 Invoke Dynamic的支持,它會為那些有幸在生產環境中已使用JDK7的開發者提高Groovy的速度;對于使用JDK 5及以上版本的所有人,則要借助靜態編譯,特別是那些打算放棄一些動態特性避免"猴子補丁"并想獲得與Java相同速度的人而言。
對于Java更友好:Java 7 Project Coin增強的支持讓Groovy和Java一如既往的表現為親密的語法表兄弟,并且在靜態類型檢查器上給予將Groovy作為Java腳本語言的開發者跟javac編譯器相同的反饋和類型安全。
模塊化更佳:借助新的模塊化級別,Groovy開啟了更小交付包的大門,例如,集成進Android上的移動應用,并允許Groovy API發展和融入新的版本和新的擴展模塊,同時還允許用戶為現有類型貢獻新的方法。