# 實戰 Groovy: Groovy 的騰飛
_熟悉 Groovy 新的、遵循 JSR 的語法_
隨著 Groovy JSR-1(及其后續發行版本)的發布,Groovy 語法的變化已經規范化 —— 這意味著如果以前沒有對此加以注意,那么現在是開始注意它的時候了。這個月,Groovy 的常駐實踐者 Andrew Glover 將介紹 Groovy 語法最重要的變化,以及在經典 Groovy 中找不到的一個方便特性。
從我在 _alt.lang.jre_的系列文章“[Feeling Groovy](http://www.ibm.com/developerworks/java/library/j-alj08034.html?S_TACT=105AGX52&S_CMP=cn-a-j)”中介紹 Groovy 開始,差不多有一年時間了。從那時起,通過發行許多版本,逐步解決了語言實現中的問題,并滿足開發人員社區的一些特性請求,Groovy 已經成熟了許多。最后,在今年四月,Groovy 有了一個重大飛躍,它正式發布了新的解析器,該解析器的目標就是將這門語言標準化為 JSR 進程的一部分。
在本月“_實戰 Groovy_”這一期的文章中,我將祝賀 Groovy 的成長,介紹通過 Groovy 非常好用的新解析器規范化的一些最重要的變化:即變量聲明和閉包。因為我要將一些新 Groovy 語法與我在關于 Groovy 的第一篇文章中介紹的經典語法進行比較,所以您可以在另一個瀏覽器窗口中打開“[alt.lang.jre: 感受 Groovy](/developerworks/cn/java/j-alj08034/)”這篇文章。
## 為什么會發生這些變化?
如果您一直在跟蹤 Groovy,不管是閱讀文章和 blog,還是自己編寫代碼,您都可能已經遭遇過這門語言的一、兩個微妙的問題。在進行一些靈敏的操作,例如對象導航,特別是使用閉包的時候,Groovy 偶爾會遇到歧義性問題和語法受限的問題。幾個月之前,作為 JSR 進程的一部分,Groovy 團隊開始著手解決這些問題。四月份,隨 groovy-1.0-jsr-01 發行版本提供的解決方案是一個更新過的語法以及一個用來對語法進行標準化的新語法內容解析器。
## 關于本系列
將任何一個工具集成到開發實踐中的關鍵是:知道什么時候使用它,什么時候把它留在箱子中。腳本語言可以是工具箱中極為強大的附件,但是只有在將它恰當應用到適當的地方時才這樣。為了實現這個目標,[_實戰 Groovy_](/developerworks/cn/views/java/articles.jsp?view_by=search&search_by=%E5%AE%9E%E6%88%98+Groovy%3A)是一個文章系列,專門介紹 Groovy 的實際應用,并教導您什么時候使用它們,以及如何成功地應用它們。
好消息是:新語法是對語言的完全增強。另一個好消息是:它和以前的語法沒有很大不同。像所有 Groovy 一樣,語法的設計目標是較短的學習曲線和較大的回報。
當然,符合 JSR 的解析器也給新 Groovy 帶來一些與目前“經典”語法不兼容的地方。如果用新的解析器運行本系列以前文章中的代碼示例,那么您自己就可以看,代碼示例可能無法工作!現在,這一點看起來可能有點苛刻 —— 特別是對 Groovy 這樣自由的語言來說 —— 但是解析器的目標是確保作為 Java 平臺的 _標準化_語言的 Groovy 的持續成長。可以把它當作新 Groovy 的一個有幫助的指南。
* * *
## Groovy 依然是 Groovy !
在深入研究變化的內容之前,我要花幾秒鐘談談什么 _沒有改變_。首先,動態類型化的基本性質沒有改變。變量的顯式類型化(即將變量聲明為 `String`或 `Collection`) 依然是可選的。稍后,我會討論對這一規則的一點新增內容。
知道分號依然是可選的時候,許多人都會感到輕松。對于放松對這個語法的使用,存在許多爭論,但是最終少數派贏得了勝利。底線是:如果愿意,也可以使用分號。
集合(Collection)的使用大部分還保持不變。仍然可以用 _array_語法和 `map`,像以前那樣(即最初從文章“[alt.lang.jre: 感受 Groovy](/developerworks/cn/java/j-alj08034/)”中學到的方式)聲明類似 `list`的集合。但范圍上略有變化,我將在后面部分展示這一點。
最后,Groovy 對標準 JDK 類的增加沒有發生多少變化。語法糖衣和漂亮的 API 也沒變, 就像普通的 Java `File`類型的情況一樣,我稍后將展示這一點。
* * *
## 容易變的變量
Groovy 的變量規則對新的符合 JSR 的語法的打擊可能最大。經典的 Groovy 在變量聲明上相當靈活(而且實際上很簡潔)。而使用新的 JSR Groovy 時,所有的變量前都必須加上 `def`關鍵字或者 `private`、`protected`或 `public`這樣的修飾符。當然,也可以聲明變量類型。另外,如果正在定義類,希望聲明屬性(使用 JavaBeans 樣式的 getter 和 setter 公開),那么也可以用 `@Property`關鍵字聲明成員變量。請注意 —— _Property_中的 _P_是大寫的!
例如,在“[alt.lang.jre: 感受 Groovy](/developerworks/cn/java/j-alj08034/)”中介紹 _GroovyBeans_時,我在文章的清單 22 中定義了一個叫做 `LavaLamp`的類型。這個類不再符合 JSR 規范,如果要運行它,則會造成解析器錯誤。幸運的是,遷移這個類不是很困難:我要做的全部工作就是給所有需要的成員變量添加 `@Property`屬性,如清單 1 所示:
##### 清單 1\. LavaLamp 的返回結果
```
package com.vanward.groovy
class LavaLamp{
@Property model
@Property baseColor
@Property liquidColor
@Property lavaColor
}
llamp = new LavaLamp(model:1341, baseColor:"Black",
liquidColor:"Clear", lavaColor:"Green")
println llamp.baseColor
println "Lava Lamp model ${llamp.model}"
myLamp = new LavaLamp()
myLamp.baseColor = "Silver"
myLamp.setLavaColor("Red")
println "My Lamp has a ${myLamp.baseColor} base"
println "My Lava is " + myLamp.getLavaColor()
```
不是太壞,不是嗎?
正如上面描述的,對于 _任何_變量,如果沒有修飾符、`@Property`關鍵字或者類型,則需要具有 `def`關鍵字。例如,清單 2 的代碼在 `toString`方法中包含一個中間變量 `numstr`,如果用 JSR 解析器運行此代碼,則會造成一個錯誤:
##### 清單 2\. 不要忘記 def 關鍵字!
```
class Person {
@Property fname
@Property lname
@Property age
@Property address
@Property contactNumbers
String toString(){
numstr = new StringBuffer()
if (contactNumbers != null){
contactNumbers.each{
numstr.append(it)
numstr.append(" ")
}
}
"first name: " + fname + " last name: " + lname +
" age: " + age + " address: " + address +
" contact numbers: " + numstr.toString()
}
}
```
認識這個代碼嗎?它借用來自“[在 Java 應用程序中加一些 Groovy 進來](/developerworks/cn/java/j-pg05245/)”一文的清單 1 中的代碼。在清單 3 中,可以看到運行代碼時彈出的錯誤消息:
##### 清單 3\. 錯誤消息
```
c:\dev\projects>groovy BusinessObjects.groovy
BusinessObjects.groovy: 13: The variable numstr is undefined in the current scope
@ line 13, column 4\.
numstr = new StringBuffer()
^
1 Error
```
當然,解決方案也是在 `toString`方法中將 `def`關鍵字添加到 `numstr`。清單 4 顯示了這個更合適的 _def_解決方案。
##### 清單 4\. 用 def 重新處理
```
String toString(){
def numstr = new StringBuffer()
if (contactNumbers != null){
contactNumbers.each{
numstr.append(it)
numstr.append(" ")
}
}
"first name: " + fname + " last name: " + lname +
" age: " + age + " address: " + address +
" contact numbers: " + numstr.toString()
}
```
另外,我還可以為 `numstr`提供一個像 `private`這樣的修飾符,或者將它聲明為 `StringBuffer`。不論哪種方法,重要的一點是:在 JSR Groovy 中,必須在變量前加上 _某些東西_。
* * *
## 封閉閉包(closure)
閉包的語法發生了變化,但是大多數變化只與參數有關。在經典的 Groovy 中,如果為閉包聲明參數,就必須用 `|`字符作為分隔符。您可能知道,`|`也是普通 Java 語言中的位操作符;結果,在 Groovy 中,只有在某個閉包的參數聲明的上下文中,才能使用 `|`字符。
在“[alt.lang.jre: 感受 Groovy](/developerworks/cn/java/j-alj08034/)”的清單 21 中,我演示了迭代,查看了用于閉包的經典 Groovy 參數語法。可以回想一下,我在集合上運用了 `find`方法,該方法試圖找到值 _3_。然后我傳入了參數 `x`,它代表 `iterator`的下一個值 (有經驗的 _Groovy_開發人員會注意到,`x`完全是可選的,我可以引用隱式變量 `it`)。使用 JSR Groovy 時,必須刪除 `|`,并用 Nice _樣式的_`->`分隔符代替它,如清單 5 所示:
##### 清單 5\. 新的 Groovy 閉包語法
```
[2, 4, 6, 8, 3].find { x ->
if (x == 3){
println "found ${x}"
}
}
```
新的閉包語法有沒有讓您想起 Nice 語言的塊語法呢?如果不熟悉 Nice 語言,請參閱 [alt.lang.jre: Nice 的雙倍功能](/developerworks/cn/java/j-alj10064.html),這是我在 _alt.lang.jre_系列上貢獻的另一篇文章。
正如我在前面提到過的,Groovy 的 JDK 沒有變。但是就像剛才所學到的,閉包卻發生了變化;所以,使用 Groovy 的 JDK 中那些漂亮的 _API_的方式也發生了變化,但僅僅是輕微的變化。在清單 6 中,可以看到這些變化對 Groovy IO 的影響根本是微不足道的:
##### 清單 6\. Groovy 的 JDK 依舊功能強大!
```
import java.io.File
new File("maven.xml").eachLine{ line ->
println "read the following line -> " + line
}
```
### 改編過濾器
現在,不得不讓您跳過很大一部分,但您還記得在“[用 Groovy 進行 Ant 腳本編程](/developerworks/cn/java/j-pg12144.html#IDAQ2BLB)”一文中,我是如何介紹閉包的威力和工具的嗎?謝天謝地,我在這個專欄的示例中所做的多數工作都很容易針對新語法重新進行改編。在清單 7 中,我只是將 `@Property`屬性添加到 `Filter`的成員 `strategy`(最初在那篇文章的清單 2 和清單 3 中顯示)。然后在閉包中添加 `->`分隔符,_萬歲_—— 它可以工作了!
##### 清單 7\. 過濾改編!
```
package com.vanward.groovy
class Filter{
@Property strategy
boolean apply(str){
return strategy.call(str)
}
}
simplefilter = { str ->
if(str.indexOf("java.") >= 0){
return true
}else{
return false
}
}
fltr = new Filter(strategy:simplefilter)
assert !fltr.apply("test")
assert fltr.apply("java.lang.String")
rfilter = { istr ->
if(istr =~ "com.vanward.*"){
return true
}else{
return false
}
}
rfltr = new Filter(strategy:rfilter)
assert !rfltr.apply("java.lang.String")
assert rfltr.apply("com.vanward.sedona.package")
```
目前為止還不壞,您覺得呢?新的 Groovy 語法很容易掌握!
* * *
## 對范圍(range)的更改
Groovy 的范圍語法的變化非常小。在經典的 Groovy 中,您可以通過使用 `...`語法指明排他性(即上界)來避開這些變化。在 JSR Groovy 中,只要去掉最后一個點(`.`),并用直觀的 `<`標識替代它即可。
請注意觀察我在下面的清單 8 中對來自“Feeling Groovy”一文中的范圍示例進行的改編:
##### 清單 8\. 新的范圍語法
```
myRange = 29..<32
myInclusiveRange = 2..5
println myRange.size() // still prints 3
println myRange[0] // still prints 29
println myRange.contains(32) // still prints false
println myInclusiveRange.contains(5) // still prints true
```
* * *
## 您是說存在歧義?
您可能注意到,在使用 Groovy 時,有一項微妙的功能可以讓您獲得方法引用,并隨意調用這個引用。可以將方法指針當作調用對象方法的方便機制。關于方法指針,有意思的事情是:它們的使用可能就表明代碼違反了迪米特法則。
您可能會問“什么是迪米特法則”呢?迪米特法則使用 _只與直接朋友對話_這句格言指出:我們應當避免調用由另一個對象方法返回的對象上的方法。例如,如果 `Foo`對象公開了一個 `Bar`對象類型,那么客戶應當 _通過 `Foo`_訪問 `Bar`的行為。結果可能是一些脆弱的代碼,因為對某個對象的更改會傳播到整個范圍。
一位受尊敬的學者寫了一篇優秀的文章,叫做“The Paperboy, the Wallet, and the Law of Demeter”(請參閱 [參考資料](#resources))。這篇文章中的示例是用 Java 語言編寫的;但是,我在下面用 Groovy 重新定義了這些示例。在清單 9 中,可以看到這些代碼演示了迪米特法則 —— 如何用它洗劫人們的錢包!
##### 清單 9\. 迪米特在行動(同情啊,同情!)
```
package com.vanward.groovy
import java.math.BigDecimal
class Customer {
@Property firstName
@Property lastName
@Property wallet
}
class Wallet {
@Property value;
def getTotalMoney() {
return value;
}
def setTotalMoney(newValue) {
value = newValue;
}
def addMoney(deposit) {
value = value.add(deposit)
}
def subtractMoney(debit) {
value = value.subtract(debit)
}
}
```
在清單 9 中,有兩個定義的類型 —— 一個 `Customer`和一個 `Wallet`。請注意 `Customer`類型公開自己的 `wallet`實例的方式。正如前面所說的,代碼簡單的公開方式存在問題。例如,如果我(像原文章的作者所做的那樣)添加了一個壞報童,去搶劫那些不知情客戶的錢包,又會怎么樣?在清單 10 中,我使用了 Groovy 的方法指針來做這件壞事。請注意我是如何使用 Groovy 新的 `&`方法指針語法,通過 `Customer`實例奪取對 `subtractMoney`方法的引用。
##### 清單 10\. 添加壞報童 ...
```
iwallet = new Wallet(value:new BigDecimal(32))
victim = new Customer(firstName:"Lane", lastName:"Meyer", wallet:iwallet)
//Didn't *ask* for a dime. Two Dollars.
victim.getWallet().subtractMoney(new BigDecimal("0.10"))
//paperboy turns evil by snatching a reference to the subtractMoney method
mymoney = victim.wallet.&subtractMoney
mymoney(new BigDecimal(2)) // "I want my 2 dollars!"
mymoney(new BigDecimal(25)) // "late fees!"
```
現在,不要誤會我:方法指針不是為了侵入代碼或者獲得對人們現金的引用!方法指針只是一種方便的機制。方法指針也很適合于重新連接上您喜愛的 80 年代的老電影!但是,如果您把這些可愛的、漂亮的東西弄濕了,那么它們可就幫不上您的忙了。嚴格地說,可以把 Groovy 的 `println`快捷方式當作 `System.out.println`的隱式方法指針。
如果一直都很留心,那么您可能已經注意到,JSR Groovy 要求我使用新的 `&`語法來創建 `subtractMoney`方法的指針。您可能已經猜到,這個添加消除了經典 Groovy 中的歧義性。
* * *
## 一些新東西!
如果在 Groovy 的 JSR 發行版中沒有什么 _新東西_,那就沒有意思了,不是嗎?謝天謝地,JSR Groovy 引入了 `as`關鍵字,它是一個方便的類型轉換機制。這個特性與新的對象創建語法關系密切,新的語法可以用類似數組的語法很容易地在 Groovy 中創建 _非定制_類。所謂非定制,指的是在 JDK 中可以找到的類,例如 `Color`、`Point`、`File`,等等。
在清單 11 中,我用這個新語法創建了一些簡單類型:
##### 清單 11\. Groovy 中的新語法
```
def nfile = ["c:/dev", "newfile.txt"] as File
def val = ["http", "www.vanwardtechnologies.com", "/"] as URL
def ival = ["89.90"] as BigDecimal
println ival as Float
```
注意,我用便捷語法創建了一個新 `File`和 `URL`,還有 `BigDecimal`,還要注意的是,我可以用 `as`把 `BigDecimal`類型轉換成 `Float`類型。
* * *
## 接下來是什么呢?
JSR 對 Groovy 的規范化過程并沒有結束,特別是在有些東西在 Groovy 的當前版本中(在本文發布時是 JSR-2)仍然不起作用的情況下。例如,在新的 Groovy 中,不能用 `do`/`while`循環。此外,新的 Groovy 還無法完全支持 Java 5.0 的 `for`循環概念。結果是,可以使用 `in`語法,但是不能使用新推出的 `:`語法。
這些都是重要的特性,不能沒有,但是不用擔心 —— Groovy 小組正在努力工作,爭取在未來幾個月內實現它們。請參閱 [參考資料](#resources),下載最新的發行版本,并學習更多關于 JSR Groovy 進程的內容;還請繼續關注下個月的“_實戰 Groovy_”,下個月我(和兩個客座專欄作家)將深入討論 Groovy 閉包的更精彩的細節。
- 實戰 Groovy
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 2 部分
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 1 部分
- 實戰 Groovy: @Delegate 注釋
- 實戰 Groovy: 使用閉包、ExpandoMetaClass 和類別進行元編程
- 實戰 Groovy: 構建和解析 XML
- 實戰 Groovy: for each 剖析
- 實戰 Groovy: Groovy:Java 程序員的 DSL
- 實戰 Groovy: 關于 MOP 和迷你語言
- 實戰 Groovy: 用 curry 過的閉包進行函數式編程
- 實戰 Groovy: Groovy 的騰飛
- 實戰 Groovy: 在 Java 應用程序中加一些 Groovy 進來
- 實戰 Groovy: 用 Groovy 生成器作標記
- 實戰 Groovy: 用 Groovy 打造服務器端
- 實戰 Groovy: 使用 Groovy 模板進行 MVC 編程
- 實戰 Groovy: 用 Groovy 進行 JDBC 編程
- 實戰 Groovy: 用 Groovy 進行 Ant 腳本編程
- 實戰 Groovy: 用 Groovy 更迅速地對 Java 代碼進行單元測試
- alt.lang.jre: 感受 Groovy