Groovy,針對JVM的類Java動態語言,如陳年好酒一樣成熟了。在2007年1月成功地發布了Groovy 1.0之後,下一個主要的裡程碑1.5版已經發布。在1.5版中有一些有趣而新穎的地方,我們會在這篇文章中考察這些特性。語言主要增強了對於Java 5特征的支持,包括注解、泛型和枚舉,這使得Groovy成為對於JVM完全支持的框架的唯一候選動態語言,框架包括Spring,Hibernate,JPA,Goole Guice或者TestNG。除了新的Java 5特性,Groovy還在語言中增加了新的語法增強,以及更強大的動態特性定制,一個基於steroids的Swing UI構建器以及改進的工具支持。
為什麼一個更加groovy的Groovy是很重要的
Groovy的關鍵賣點始終是它與Java的無縫集成。你能夠很容易地把Groovy和Java的類混合搭配:你可以讓一個Java類實現一個Groovy接口,然後讓一個Groovy類繼承那個Java類,或者相反。不幸的是,絕大多數其他的候選的JVM語言不能讓你無縫的在兩種不同的語言之間交換類。因此,如果你希望為工作使用最好的語言而不放棄優美的類的層次結構,你沒有太多的選擇,而Groovy使得你可以自由的將兩種語言以幾乎透明的方式集成在一起。
Groovy與Java共享同樣的庫,同樣的對象模型,同樣的線程模型,同樣的安全模型。在某種意義上,你可以認為Groovy是你的Java項目的一個實現細節,而不必忍受阻抗失配問題。
Groovy就是Java,而且Groovy使得Java更groovy了。與其他語言相比,Groovy對於Java開發者無疑提供了最平滑的學習曲線,這得益於兩者非常相似的語法。
需要牢記的是Groovy產生的是正常的Java字節碼而且使用普通的JDK庫,所以你不需要學習全部的新的API而且不需要復雜的集成機制:極其方便,Groovy和Java是可以相互交換的。附加的好處是你可以保護對你的Java開發人員Java技巧方面的投資,或者是昂貴的應用服務器,或者第三方的或者公司自己開發的庫,你可以在Groovy中毫無問題地重用他們。
其他不支持強類型的候選語言,在調用JDK、第三方庫或者公司自己的庫的時候,由於它們不能辨別同一方法的某一多態變種,所以始終不能調用所有的Java方法。當你選擇一種語言來提高你的生產率或者使你的代碼可讀性更強的時候,如果你需要調用其他Java類,你必須非常謹慎的選擇語言,因為可能會碰到很多麻煩。
今天,所有主要的企業框架都需要使用注解、枚舉或者泛型這樣的語言特性來充分提高它們的效率。幸運的是,開發者使用Groovy1.5的話就可以在他們的項目中使用所有的Java 5特性並因此而獲益。讓我們看看在Groovy中如何使用注解,枚舉和泛型。
Java 5增加的部分
Groovy編譯器始終產生與以前的Java VM兼容的Java字節碼,但是由於Groovy使用了JDK1.4的核心庫,所以Groovy依賴於JDK1.4。然而,對於這些Java 5中增加的部分,肯定需要使用Java 5的字節碼。例如,產生的類中也許包含代表著運行時保留策略注解的字節碼信息。所以,雖然Groovy1.5能夠在JDK1.4上運行,但是某些Groovy的特征只能在JDK1.5上使用 —— 出現這種情況時,本文會作出聲明。
可變的參數
在Java 5中創建了省略號表示法,代表方法的參數是可變長度的。通過三個小圓點,Java允許用戶在一個方法的末端輸入相同類型的任意數量的參數 —— 實際上,可變長度參數(vararg)就是一個那種類型的元素的數組。可變長度參數在Groovy 1.0中已經出現了 —— 現在仍然可以在JDK1.4運行時環境下工作,1.0足以向你展示如何來使用他們了。基本上,只要當一個方法的最後一個參數是一個對象數組,或者是一個有三個點的參數,你就可以向這個方法傳入多重參數。
第一個例子介紹了在Groovy中用省略號來使用可變長度變量的方法:
int sum(int... someInts) {
def total = 0
for (int i = 0; i < someInts.size(); i++)
total += someInts[i]
return total
}
assert sum(1) == 1
assert sum(1, 2) == 3
assert sum(1, 2, 3) == 6
這個例子中所用的斷言顯示了我們如何傳入任意多的int類型的參數。還有一個有趣的地方,為了更好的兼容Java語法,Java中經典的循環方式也加入了Groovy中 —— 盡管在groovy中更有groovy特色的循環是用in關鍵字,同樣可以透明地遍歷各種各樣的數組或者集合類型。
請注意使用一個數組作為最後一個參數同樣可以支持可變長度變量,就像下面這樣聲明方法:
int sum(int[] someInts) { /* */ }
這個代碼片斷是非常無聊的。很明顯有很多更有表現力的方式來計算一個總和。例如,如果你有一個數字的列表,你可以在一行代碼中計算他們的總和:
assert [1, 2, 3].sum() == 6
在Groovy中可變長度變量不需要JDK 5作為基本的Java運行時環境,在下面的章節中我們要介紹的注解則需要JDK 5。
注解
正如在JBoss Seam的文檔中所介紹的那樣,Seam支持使用Groovy來寫Seam的實體,控制器和組件,類似@Entity,@Id,@Override以及其他的注解可以用來修飾你的bean:
@Entity
@Name("hotel")
class Hotel implements Serializable
{
@Id @GeneratedValue
Long id
@Length(max=50) @NotNull
String name
@Length(max=100) @NotNull
String address
@Length(max=40) @NotNull
String city
@Length(min=2, max=10) @NotNull
String state
@Length(min=4, max=6) @NotNull
String zip
@Length(min=2, max=40) @NotNull
String country
@Column(precision=6, scale=2)
BigDecimal price
@Override
String toString() {
return "Hotel(${name}, ${address}, ${city}, ${zip})"
}
}
Hotel實體用@Entity注解來標識,用@Name給了它一個名字。可以向你的注解傳遞不同的參數,例如在@Length注解約束中,為了做有效性檢查可以給注解設置不同的上界和下界。在實例中你還會注意到Groovy的屬性:getter方法和setter方法都到哪裡去了?公有或者私有的修飾符在哪裡?你不必等待Java 7或者Java 8來獲得屬性!在Groovy中,按照慣例,定義一個屬性非常簡單:String country:這樣就會自動生成一個私有的country成員變量,同時生成一個公有的getter和setter方法。你的代碼自然而然的變得簡潔而易讀。
在Groovy中,注解可以象在Java中一樣用在類、成員變量、方法和方法參數上。但是,有兩個很容易犯錯誤的地方需要小心。第一,你可以在Groovy中用注解,可是你不能定義它們 —— 然而,在一個快要到來的Groovy版本中將可以定義注解。第二,雖然Groovy的語法幾乎與Java的語法100%相同,但是在注解中傳入一個數組作為參數時還是有一點點不同:Groovy不是用圓括號來括起元素,而是需要使用方括號,目的是為了提供更一致的語法 —— 在Groovy中列表和數組都用方括號來括起他們的元素。
通過Groovy1.5中的注解,你可以在Groovy中方便地為JPA或者Hibernate定義你的的帶注解的bean(http://www.curious-creature.org/2007/03/25/persistence-made-easy-with-groovy-and-jpa/),在你的Spring服務上增加一個@Transactional 注解,使用TestNG和Fest來測試你的Swing UI(http://www.jroller.com/aalmiray/entry/testing_groovy_uis_with_fest)。在Groovy項目中你可以使用所有支持注解的有用而強大的企業框架。
枚舉
當你需要一組固定數量的相同類型的常量時,枚舉是很方便的。例如你需要一種干淨的方式來為日期定義常量而不借助使用整數常量,那麼枚舉是你的好幫手。下面的片斷顯示了如何定義一星期中的日子:
enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY
}
一旦你定義了你的枚舉,你可以在Java中以通常的記法Day.MONDAY來使用它,還可以使用枚舉來潤色你的switch/case語句:
def today = Day.SATURDAY
switch (today) {
// Saturday or Sunday
case [Day.SATURDAY, Day.SUNDAY]:
println "Weekends are cool"
break
// a day between Monday and Friday
case Day.MONDAY..Day.FRIDAY:
println "Boring work day"
break
default:
println "Are you sure this is a valid day?"
}
請注意Groovy的switch語句比類似C風格語言的switch語句要強大一些,在Groovy中可以在switch和case語句使用任何類型的對象。不用為每一個枚舉值羅列七個不同的case語句塊,你可以在列表或者ranges(Groovy集合類的一種類型)中重新分組case語句:當值出現在列表或者range中,case將為真而且會執行它關聯的命令。
受到Java教程的啟示,這裡是一個更復雜的關於天文學的例子,向你展示了在枚舉中如何包含屬性,構造器和方法:
enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7)
double mass
double radius
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
void printMe() {
println "${name()} has a mass of ${mass} " +
"and a radius of ${radius}"
}
}
Planet.EARTH.printMe()
與注解一樣,由於產生了Java 5的字節碼,Groovy中的枚舉需要JDK 5+的環境才能運行。
靜態導入
在前面關於枚舉的例子中,我們始終需要在枚舉值的前面加上它的父枚舉類,但是通過靜態導入(可以在JDK1.4運行時環境上工作)我們可以去掉Planet前綴,從而節省一些字符。
import static Planet.*SATURN.printMe()
這樣就不再需要Planet前綴。當然,靜態導入不僅僅對枚舉有效,對其他類和靜態成員變量同樣有效。我們不妨作些數學計算。
import static java.lang.Math.*assert sin(PI / 6) + cos(PI / 3) == 1
java.lang.Math的靜態方法和靜態常量都被靜態導入了,這樣使得表達式更加簡明。但是如果sine和cosine的縮寫不便於你閱讀,那麼你可以使用Groovy中的as關鍵字來做別名:
import static java.lang.Math.PI
import static java.lang.Math.sin as sine
import static java.lang.Math.cos as cosine
assert sine(PI / 6) + cosine(PI / 3) == 1
別名不僅僅用於靜態導入,也可以用於正常的導入,是很有用的方法。例如在很多框架中有名字非常長的類,可以使用別名來增加快捷記法,或者重命名名字不太直觀的方法或者常量,或者重命名與你的命名約定標准不一致的方法或常量。
泛型
在Java 5中有爭議的特性:泛型,也出現在Groovy 1.5的最新版本中。畢竟,開始的時候可能覺得在一個動態語言中加入更多類型信息是多余的。Java開發人員通常相信因為類型擦除(為了向後兼容Java以前的版本)使得在類的字節碼中沒有保留代表泛型的類型信息。然而,這是錯誤的看法,通過反射API,你可以內省一個類從而發現它的成員變量類型或者它的有泛型詳細信息的方法參數類型。
例如,當你聲明了類型為List的成員變量時,這個信息是在字節碼的某個地方以某種元信息的方式保存的,盡管這個成員變量確實僅僅是List類型的。這種反射信息被諸如JPA或者Hibernate這樣的企業框架所使用,將一個元素的集合中的實體關聯到代表這些元素的類型的實體。
為了實踐這些理論,讓我們檢查泛型信息是否保存在類的成員變量中。
class Talk {
String title
}
class Speaker {
String name
List talks = []
}
def me = new Speaker(
name: 'Guillaume Laforge',
talks: [
new Talk(title: 'Groovy'),
new Talk(title: 'Grails')
])
def talksField = me.class.getDeclaredField('talks')
assert talksField.genericType.toString() ==
'java.util.Listt'
我們定義了兩個類:一個在會議上給出Talk的Speaker類。在Speaker類中,talks屬性的類型是List。然後,我們創建了一個Speaker實例,用兩個優美的捷徑來初始化name和talks屬性,並創建了一個Talk實例的列表。當初始化代碼就緒後,我們取得代表talks的成員變量,然後檢查泛型信息是否正確:正確!talks是一個List,但是它是一個Talk的List。
共變的返回類型
在Java 5中,如果你在一個子類中有一個方法,其名稱與參數類型與父類中的方法相同,但是返回值是父類方法的返回值的子類,那麼我們可以覆蓋父類的方法。在Groovy1.0中,不支持共變的返回類型。但是在Groovy1.5中,你可以使用共變返回類型。而且,如果你試圖覆蓋一個方法而返回類型不是父類方法的返回類型的子類,將拋出一個編譯錯誤。共變的返回類型對於參數化的類型同樣有效。
除了因為支持Java 5的特性而給Groovy語言帶來了一些增強外,Groovy1.5還引入了其他一些語法的增強,我們在下面的章節中會探索這些部分。
增加的語法
Elvis操作符
Java 5的特性除了帶給Groovy注解,泛型和枚舉,還增加了一個新操作符—— ?:,Elivis操作符。當你看這個操作符的時候,你很容易猜測為什麼會這樣命名 —— 如果不是,可以根據Smiley來思考。這個新操作符實際上是一個三目操作符的便捷記法。你是否經常使用三目操作符來改變一個變量的值?如果它是null那麼給它分配一個缺省值。在Java中典型的情況是這樣的:
String name = "Guillaume";
String displayName = name != null ? name : "Unknown";
在Groovy中,由於語言本身可以按需“強制”類型轉換到布爾值(例如在if或者while構造中條件表達式需要為布爾值),在這個語句中,我們可以忽略和null的比較,因為當一個String是null的時候,它被強制轉換為false,所以在Groovy中語句會變為:
String name = "Guillaume"
String displayName = name ? name : "Unknown"
然而,你仍然會注意到name變量的重復,這破壞了DRY原則(不要重復你自己 Don't Repeat Yourself)。由於這個構造非常普遍,所以引入了Elvis操作符來簡化這些重復的現象,語句變成:
String name = "Guillaume"
String displayName = name ?: "Unknown"
name變量的第二次出現被簡單的忽略了,三目操作符不再是三目的了,縮短為這種更簡明的形式。
還有一點值得注意的是這個新的構造沒有副作用,由於第一個元素(這裡是name)不會象在三目操作符中那樣被估值兩次,所以不需要引入一個中間的臨時變量來保持三目操作符中第一個元素的第一次估值。
經典的循環
雖然Groovy嚴格地來說不是100%的Java的超集,但是在每一個Groovy的新版本中,其語法都更接近Java的語法,在Groovy中越來越多的Java代碼是有效的。這種兼容性的好處是當你開始用Groovy工作時,你可以拷貝並粘貼Java代碼到你的Groovy類中,它們會如你所願地工作。然後,隨著時間的推移你學習了Groovy語言,你可以扔掉那些從Java拷貝來的在Groovy中不地道的代碼,使用GStrings(內插字符串),或者閉包等等。Groovy為Java開發者提供了一個非常平滑的學習曲線。
然而,Groovy中有一處忽略了Java語法的兼容性,實際上Groovy中不允許使用從Java語言的C背景繼承而來的經典的循環語法。最初,Groovy開發者認為經典的循環語法不是最好的,他們更喜歡使用可讀性更好的for/in構造。但是由於Groovy用戶經常要求Groovy包含這個舊的循環構造,所以Groovy團隊決定支持它。
在Groovy 1.5中,你可以選擇Groovy的for/in構造,或者經典的for循環構造:
for (i in 0..9)
println i
for (int i = 0; i < 10; i++)
println i
最終,這也許只是品味不同,Groovy的熟手用戶通常更喜歡for/in循環這樣更加簡明的語法。
沒有圓括號的命名參數
由於易適應且簡明的語法,以及高級的動態能力,Groovy是實現內部領域特定語言(Domain-Specific Languages)的理想選擇。當你希望在業務問題專家和開發者之間共享一種公共的比喻說法的時候,你可以借Groovy之力來創建一個專用的商業語言,用該語言為你的應用的關鍵概念和商業規則建模。這些DSL的一個重要方面是使得代碼非常可讀,而且讓非技術人員更容易寫代碼。為了更進一步實現這個目標,Groovy的語法做了通融,允許我們使用沒有圓括號括起來的命名參數。
首先,在Groovy中命名參數看起來是這樣的:
fund.compare(to: benchmarkFund, in: euros)
compare(fund: someFund, to: benchmark, in: euros)
通過向數字加入新的屬性 —— 這在Groovy中是可能的,但是超出了這篇文章的范圍 —— 我們可以寫出像這樣的代碼:
monster.move(left: 3.meters, at: 5.mph)
現在通過忽略圓括號,代碼變得更清晰了:
fund.compare to: benchmarkFund, in: euros
compare fund: someFund, to: benchmark, in: euros
monster.move left: 3.meters, at: 5.mph
顯然,這沒有很大的區別,但是每個語句變得更接近淺白的英語句子,而且在宿主語言中刪除了通常冗余的技術代碼。Groovy語言這個小小的增強給予了商業DSL設計人員更多的選擇。
改善的工具支持
當Groovy還不成熟的時候,一個常見的弱點是缺乏好的工具支持:工具系列和IDE支持都不到位。幸運的是,隨著Groovy和Grails web框架的成熟和成功,這種狀況得到了改變。
“聯合”編譯器的介紹
Groovy以它與Java的透明而且無縫的集成而聞名。但是這不僅僅意味著在Groovy腳本中可以調用Java方法,不,兩個語言之間的集成遠不止於此。例如,一個Groovy類繼承一個Java類,而該Java類實現一個Groovy接口是完全可能的,反之亦然。不幸的是,其他候選語言不支持這樣做。然而,到目前為止,當把Groovy和Java混合起來使用的時候,你在編譯時要小心選擇正確的編譯順序,如果兩個語言中出現循環依賴,那麼你也許會碰到一個“雞與蛋”的問題。幸運的是在Groovy 1.5中這不再是問題,謝謝獲獎的Java IDE IntelliJ IDEA的創建者JetBrains的一個貢獻,你可以使用一個“聯合”編譯器將Groovy和Java代碼放在一起一次編譯而不必考慮類之間的依賴關系。
如果你希望在命令行使用聯合編譯器,你可以像通常那樣調用groovyc命令,但是使用-j參數來進行聯合編譯:
groovyc *.groovy *.java -j -Jsource=1.4 -Jtarget=1.4
為了向基本的javac命令傳遞參數,你可以用J作為參數的前綴。你還可以在你的Ant或者Maven構建文件中使用聯合編譯器執行Ant任務:
<taskdef name="groovyc"
classname="org.codehaus.groovy.ant.Groovyc"
classpathref="my.classpath"/>
<groovyc
srcdir="${mainSourceDirectory}"
destdir="${mainClassesDirectory}"
classpathref="my.classpath"
jointCompilationOptions="-j -Jsource=1.4 -Jtarget=1.4" />
Groovy的Maven插件
對於Maven用戶,在Codehaus有一個全特性的Maven插件項目允許你構建自己的Java/Groovy應用:編譯你的Groovy和Java代碼,從JavaDoc標簽生成文檔,甚至允許你在Groovy中開發自己的Maven插件。還有一個Maven的原型可以更迅速的引導你的Groovy項目。要得到更多信息,你可以參考插件的文檔:http://mojo.codehaus.org/groovy/index.html
GroovyDoc文檔工具
作為一個Java開發人員,你習慣於通過你的類,接口,成員變量或者方法的注釋中的JavaDoc標簽來生成代碼文檔。在Groovy中,你仍然可以在你的注釋中使用這樣的標簽,使用一個叫做GroovyDoc的工具為你所有的Groovy類生成與JavaDoc同樣的文檔。
這裡有一個Ant任務,你可以定義並用它來產生文檔:
<taskdef name="groovydoc"
classname="org.codehaus.groovy.ant.Groovydoc">
<classpath>
<path path="${mainClassesDirectory}"/>
<path refid="compilePath"/>
</classpath>
</taskdef>
<groovydoc
destdir="${docsDirectory}/gapi"
sourcepath="${mainSourceDirectory}"
packagenames="**.*" use="true"
windowtitle="Groovydoc" private="false"/>
新的交互性shell和Swing控制台
Groovy的發行版本總是包含兩個不同的shell:一個命令行shell和一個Swing控制台。命令行shell,Groovysh,就其與用戶的交互性而言從來都不是很友好:當你希望執行一個語句的時候,你不得不在每個語句後面鍵入“go”或者“execute”,這樣才能執行。為了某些快速的原型開發或者試用一些新的API,每次都鍵入“go”是非常累贅的。在Groovy 1.5中情況變化了,有了新的交互式的shell。不再需要鍵入“go”。
這個新的shell有幾個增強的特性,例如使用了提供ANSI著色的JLine庫,tab命令補全,行編輯能力。你可以與不同的腳本緩沖器工作,記住已經導入的類,裝載現存的腳本,將當前腳本保存到一個文件中,浏覽歷史記錄,等等。欲得到shell所支持特性的更詳細解釋,請參閱文檔。
不僅僅命令行shell得到了提高,Swing控制台也有改進,有了新的工具條,先進的undo能力,可以增大或者縮小字體,語法高亮等,總之,控制台有了很多提高。
IntelliJ IDEA JetGroovy 插件
JetGroovy插件是最棒的工具支持:一個免費而且開源的專用於支持Groovy和Grails的IntelliJ IDEA插件。這個插件是由JetBrains他們自己開發的,對於語言和Web框架都提供了無以倫比的支持。
插件對Groovy有專門的支持,其中部分特性:
◆對於所有的語法都可以語法高亮,對於未識別的類型加不同的警告。
◆可以運行Groovy類,腳本和用Groovy寫的JUnit測試用例。
◆調試器:你可以一步一步地運行你的Java和Groovy代碼,設置斷點,顯示變量,當前的堆棧信息等等。
◆聯合編譯器:編譯器將Groovy和Java一起編譯,可以解決語言之間的依賴問題。
◆代碼補全,可以補全包,類,屬性,成員變量,變量,方法,關鍵字,甚至對於Swing UI builder有特殊的支持。
◆先進的類搜索和發現功能。
◆重構:大多數在Java中你所喜愛的常用重構功能都可以在Java和Groovy中使用,例如“surround with”,介紹、內聯或者重命名一個變量,重命名包、類、方法和成員變量。
◆導入優化和代碼格式化。
◆結構視圖:對你的類有一個鳥瞰視圖。
最終,考慮到在IntelliJ IDEA中提供的支持和相互影響的程度,你甚至不會意識到你是在Groovy中還是在Java中開發一個類。如果你正在考慮在你的Java項目中增加一些Groovy或者你打算開發Grails應用,這個插件是肯定要安裝的。
你可以在JetBrains站點得到更多信息。
盡管我僅僅表揚了IntelliJ IDEA的Groovy插件,但是你不必因此改變你的Groovy開發習慣。你可以使用由IBM的Zero項目開發者持續改進的Eclipse插件,或者Sun的NetBeans的Groovy和Grails插件。
性能提高
Groovy的新版本除了增加新特性,與以前的版本相比還顯著地提高了性能,並且降低了內存消耗。在我們的非正式的基准測試中,我們發現與Groovy 1.5 beta版相比我們所有測試套件的運行速度有了15%到45%的提高 —— 與Groovy 1.0相比肯定有更多的提高。雖然還需要開發更正式的基准測試,但是一些開發人員已經證實了這些測試數字,一家保險公司的開發人員正在使用Groovy來寫他們的策略風險計算引擎的商業規則,另一個公司在高並發機器上運行了多個測試。總的來說,Groovy在絕大多數情況下會更快,更輕盈。不過在具體的應用中,效果還要看你如何使用Groovy。
增強的動態能力
由於Groovy和Grails項目的共生關系,Grails核心部分中成熟的動態能力已經被引入到Groovy中。
Groovy是一個動態語言:簡單的說,這意味著某些事情,例如方法分派發生在運行時,而不是象Java和其他語言那樣發生在編譯時。在Groovy中有一個特殊的運行時系統,叫做MOP(元對象協議Meta-Object Protocol),負責方法分派邏輯。幸運的是,這個運行時系統非常開放,人們可以深入系統並且改變系統的通常行為。對於每一個Java類和每一個Groovy實例,都有一個與之相關聯的元類(meta-class)代表該對象的運行時行為。Groovy為你與MOP交互提供了幾種不同的方法,可以定制元類,可以繼承某些基類,但是謝謝Grails項目的貢獻,有一種更groovy的元類:expando元類。
代碼例子可以幫助我們更容易地理解概念。在下面的例子中,字符串msg的實例有一個元類,我們可以通過metaClass屬性訪問該元類。然後我們改變String類的元類,為其增加一個新方法,為toUpperCase()方法提供一個速記記法。之後,我們為元類的up屬性分配一個閉包,這個屬性是在我們把閉包分配給它的時候創建的。這個閉包沒有參數(因此它以一個箭頭開始),我們在閉包的委托之上調用toUpperCase()方法,這個委托是一個特殊的閉包變量,代表著真實的對象(這裡是String實例)。
def msg = "Hello!"
println msg.metaClass
String.metaClass.up = { -> delegate.toUpperCase() }
assert "HELLO!" == msg.up()
通過這個元類,你可以查詢對象有哪些方法或者屬性:
// print all the methods
obj.metaClass.methods.each { println it.name }
// print all the properties
obj.metaClass.properties.each { println it.name }
你甚至可以檢查某個特定的方法或者屬性是否可用,比使用instanceof來檢查的粒度要小的多:
def msg = 'Hello!'
if (msg.metaClass.respondsTo(msg, 'toUpperCase')) {
println msg.toUpperCase()
}
if (msg.metaClass.hasProperty(msg, 'bytes')) {
println foo.bytes.encodeBase64()
}
這些機制在Grails web框架中得到了廣泛的使用,例如創建一個動態查找器:由於你可以在一個Book領域類上調用一個findByTitle()動態方法,所以在大多數情況下不需要DAO類。通過元類,Grails自動為領域類加入了這樣的方法。此外,如果被調用的方法不存在,在第一次調用的時候方法會被創建並緩存。這可以由下面解釋的其他高級技巧來完成。
除了我們已經看到的例子,expando元類也提供了一些補充的功能。在一個expando元類中可以加入四個其他方法:
◆invokeMethod() 讓你可以攔截所有的方法調用。
◆而methodMissing() 僅僅在沒有發現其他方法的時候被調用。
◆get/setProperty() 攔截對所有屬性的訪問。
◆而propertyMissing()在沒有發現屬性的時候被調用。
與以前的Groovy版本相比,通過expando元類可以更容易定制你的應用行為,並且節約昂貴的開發時間。很明顯,不是每個人都需要使用這些技術,但是在許多場合這些技術是很方便的,例如你想應用某些AOP(面向方面的編程Aspect Oriented Techniques)來裝飾你的類,或者想通過刪除某些不必要的冗余代碼來簡化你的應用的商業邏輯代碼並使其可讀性更強。
Steroids之上的Swing
Groovy項目有一個天才的Swing開發者團隊,他們努力工作使得在Groovy中用Swing來構建用戶界面的能力更強大。在Groovy中構建Swing UI的基石是SwingBuilder類:在你的代碼中,你可以在語法級別可視化的看到Swing組件是如何彼此嵌套的。Groovy web站點的一個過分簡單的例子顯示了如何簡單地創建一個小的GUI程序:
import groovy.swing.SwingBuilder
import java.awt.BorderLayout
import groovy.swing.SwingBuilder
import java.awt.BorderLayout as BL
def swing = new SwingBuilder()
count = 0
def textlabel
def frame = swing.frame(title:'Frame', size:[300,300]) {
borderLayout()
textlabel = label(text:"Clicked ${count} time(s).",
constraints: BL.NORTH)
button(text:'Click Me',
actionPerformed: {count++; textlabel.text =
"Clicked ${count} time(s)."; println "clicked"},
constraints:BorderLayout.SOUTH)
}
frame.pack()
frame.show()
Swing構建器的概念已經擴展到提供定制的組件工廠。有一些不是缺省包含在Groovy中的附加模塊,它們把JIDE或者SwingX項目中的Swing組件集成到Swing構建器代碼中。
在這個版本中,界面部分有很多改進,值得用一整篇文章來敘述。我僅僅列出其中一部分,例如bind()方法。受到JSR (JSR-295)的bean綁定(beans binding)的啟發,你可以很容易地將組件或者bean綁定到一起,使得它們在對方發生變化的時候作出反應。在下面的例子中,按鈕的間隔尺寸會根據滾動條組件的值的變化而變化。
import groovy.swing.SwingBuilder
import java.awt.Insets
swing = new SwingBuilder()
frame = swing.frame {
vbox {
slider(id: 'slider', value:5)
button('Big Button?!', margin:
bind(source: slider,
sourceProperty:'value',
converter: { [it, it, it, it] as Insets }))
}
}
frame.pack()
frame.size = [frame.width + 200, frame.height + 200]
frame.show()
在構建用戶界面的時候將組件綁定在一起是非常常見的任務,所以這個任務通過綁定機制被簡化了。還可以使用其他的自動綁定方法,但是需要一篇專門的文章來闡述。
在其他新的值得注意的特性中,新增了一些方便的方法,使得閉包可以調用聲名狼籍的SwingUtilities類,啟動新的線程:edt()將調用invokeAndWait()方法, doLater()將會調用invokeLater()方法,doOutside()方法會在一個新線程中啟動一個閉包。不再有丑陋的匿名內部類:只要通過這些便捷方法使用閉包就可以!
最後但是也最重要的是,由於SwingBuilder的build()方法,分離視圖的描述與它相關聯的行為邏輯變成再簡單不過的事情了。你可以創建一個僅僅包含視圖的單獨的腳本,而與組件的交互或者綁定都在主類中,在MVC模式中可以更清晰的分離視圖與邏輯部分。
總結
這篇文章列出了Groovy 1.5中引人注目的新特性,但是我們僅僅觸及了Groovy這個新版本的皮毛。重要的亮點主要圍繞著Java 5的新特性,例如注解、枚舉或者泛型:這使得Groovy可以完美地與諸如Spring、Hibernate或者JPA這樣的企業框架優美而無縫的集成。得益於改進的語法以及增強的動態能力,Groovy讓你能夠創建內嵌的領域特定語言來定制你的商業邏輯,並在應用的擴展點將其方便地集成進來。由於工具支持的大幅改善,開發者的體驗有了顯著的提高,開發體驗不再是采用Groovy的一個障礙。總的來說,Groovy 1.5前所未有的滿足了簡化開發者生活的目標,Groovy應該成為所有Java開發者工具箱的一部分。
關於作者
Guillaume Laforge是Groovy的項目經理和JSR-241規范的領導者,Java規范請求(Java Specification Request)在Java社區過程(Java Community Process)中標准化了Groovy語言。他還是Technology的副主席以及G2One, Inc.的核心創建者,該公司資助並領導著Groovy和Grails項目的發展。Guillaume經常在不同的會議談論Groovy和Grails,例如JavaOne,JavaPolis,Sun TechDays,Spring Experience,Grails eXchange。