什么是 Groovy?
簡(jiǎn)言之,Groovy是一種基于JVM(Java虛擬機(jī))的敏捷動(dòng)態(tài)開(kāi)發(fā)語(yǔ)言。它是一種成熟的面向?qū)ο?/a>編程語(yǔ)言,既可以用于面向?qū)ο缶幊蹋挚梢杂米骷兇獾?a target="_blank" rel="nofollow">腳本語(yǔ)言。作為Java程序員,即便以前沒(méi)有接觸過(guò)Groovy,也可以快速學(xué)習(xí)。
Groovy 和 Java 語(yǔ)言對(duì)比
用 Java 編寫(xiě)的典型的 Hello World 示例如下所示:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("java:Hello World");
? ?}
}
用 Groovy 編寫(xiě)的 Hello World
Groovy 支持松散的 Java 語(yǔ)法 — 例如,不需要為打印 “Hello World!” 這樣的簡(jiǎn)單操作定義類。
而且,Groovy 使日常的編碼活動(dòng)變得更容易,例如,Groovy 允許輸入println,而無(wú)需輸入System.out.println。當(dāng)您輸入println時(shí),Groovy 會(huì)非常聰明地知道您指的是System.out。
所以,用 Groovy 編寫(xiě) Hello World 程序就如下面這樣簡(jiǎn)單:
println "Hello World!"
事實(shí)證明,使用Groovy可以達(dá)到事半功倍的效果!
? ?本教程采用的是在eclipse中安裝Groovy插件,(具體安裝方法見(jiàn)百度),通過(guò)建立Groovy項(xiàng)目,和Java實(shí)例進(jìn)行比較來(lái)學(xué)習(xí)。
基礎(chǔ)
Groovy注釋標(biāo)記和Java一樣,支持//或者//
Groovy語(yǔ)句可以不用分號(hào)結(jié)尾。
Groovy中支持動(dòng)態(tài)類型,即定義變量的時(shí)候可以不指定其類型。Groovy中,變量定義可以使用關(guān)鍵字def。
def var=1
def str="iamaperson"
def intx=1 ?//也可以指定類型
函數(shù)定義時(shí),參數(shù)的類型也可以不指定。比如
Stringfunction(arg1,args2){//無(wú)需指定參數(shù)類型}
除了變量定義可以不指定類型外,Groovy中函數(shù)的返回值也可以是無(wú)類型的。比如:
//無(wú)類型的函數(shù)定義,必須使用def關(guān)鍵字
def nonReturnTypeFunc(){?
?? last_line//最后一行代碼的執(zhí)行結(jié)果就是本函數(shù)的返回值
}
//如果指定了函數(shù)返回類型,則可不必加def關(guān)鍵字來(lái)定義函數(shù)
String getString(){
return "I am a string"
}
Groovy是基于Java的,而且最終會(huì)轉(zhuǎn)成Java Code運(yùn)行在JVM上
Groovy對(duì)字符串支持相當(dāng)強(qiáng)大,充分吸收了一些腳本語(yǔ)言的優(yōu)點(diǎn):
1 單引號(hào)''中的內(nèi)容嚴(yán)格對(duì)應(yīng)Java中的String,不對(duì)$符號(hào)進(jìn)行轉(zhuǎn)義
def ? singleQuote='Iam$dolloar' ? ? ? ? ?//輸出就是Iam$dolloar
2 雙引號(hào)""的內(nèi)容則和腳本語(yǔ)言的處理有點(diǎn)像,如果字符中有$號(hào)的話,則它會(huì)$表達(dá)式先求值。
def ? doubleQuoteWithoutDollar="I am one dollar" ? ?//輸出 I am one dollar
def x=1
def ? doubleQuoteWithDollar="I am $x dolloar" ? ? ? ?//輸出I am 1 dolloar
3 三個(gè)引號(hào)'''xxx'''中的字符串支持隨意換行 比如
def multieLines =''' begin
line? 1
line? 2
end '''
最后,除了每行代碼不用加分號(hào)外,Groovy中函數(shù)調(diào)用的時(shí)候還可以不加括號(hào)。比如:
println("test")---> println ?"test"
注意,雖然寫(xiě)代碼的時(shí)候,對(duì)于函數(shù)調(diào)用可以不帶括號(hào),但是Groovy經(jīng)常把屬性和函數(shù)調(diào)用混淆。比如
def ?getSomething(){"hello"}
get ? Something() //如果不加括號(hào)的話,Groovy會(huì)誤認(rèn)為getSomething是一個(gè)變量。
所以,調(diào)用函數(shù)要不要帶括號(hào),個(gè)人覺(jué)得如果這個(gè)函數(shù)是Groovy API或者Gradle API中比較常用的,比如println,就可以不帶括號(hào)。否則還是帶括號(hào)。
Groovy類:
1,不需要public修飾
2,不需要類型說(shuō)明
3,不需要get/set方法
4,不需要構(gòu)造函數(shù)
5,不需要return——默認(rèn)返回方法中的最后一行
6,方法調(diào)用時(shí)可以省略() ? //構(gòu)造函數(shù)除外
Groovy 是沒(méi)有類型的 Java 代碼
在 Java 中,如果要聲明一個(gè)String變量,則必須輸入:
String value = "Hello World";
但是,如果仔細(xì)想想,就會(huì)看出,等號(hào)右側(cè)的字符已經(jīng)表明value的類型是String。所以,Groovy 允許省略value前面的String類型變量,并用def代替。
def value = "Hello World"
實(shí)際上,Groovy 會(huì)根據(jù)對(duì)象的值來(lái)判斷它的類型。
將 HelloWorld.groovy 文件中的代碼編輯成下面這樣:
String message = "Hello World"
println message
運(yùn)行這段代碼,應(yīng)該會(huì)在控制臺(tái)上看到與前面一樣的 “Hello World”。現(xiàn)在,將變量類型String替換為def并重新運(yùn)行代碼。是不是注意到了相同的結(jié)果?
除了輸出message的值,還可以用以下調(diào)用輸出它的類型:
println message.class
輸出 “class java.lang.String” ? ? ? Groovy 推斷出message一定是String類型的,因?yàn)樗闹凳怯秒p引號(hào)括起來(lái)的。
再來(lái)看如下賦值:
def message = 12
println message.class
message變量的數(shù)字值看起來(lái)像是 Java 的原生類型int。但是,運(yùn)行這個(gè)代碼就可以看出,Groovy 將它作為Integer。這是因?yàn)樵?Groovy 中 “一切都是對(duì)象”。
Java 中的所有對(duì)象都擴(kuò)展自java.lang.Object,這對(duì) Groovy 來(lái)說(shuō)非常方便。即使在最糟的情況下,Groovy 運(yùn)行時(shí)不能確定變量的類型,它只需將變量當(dāng)成Object,問(wèn)題就解決了。
繼續(xù)使用這段代碼。將message改成自己喜歡的任意類型:Groovy 會(huì)在運(yùn)行時(shí)盡其所能推斷出這個(gè)變量的類型。
無(wú)類型有什么意義?
Groovy 缺少類型意味著所需的輸入更少。更重要的是,這意味著要閱讀的代碼要少得多。最后,Groovy 缺少類型能夠帶來(lái)更高的靈活性 — 不需要接口或抽象類。
所以,只需要使用def關(guān)鍵字就能在方法中聲明一個(gè)獨(dú)立變量,不需要將def關(guān)鍵字作為方法聲明中的參數(shù)。在for循環(huán)聲明中也不需要它,這意味著不用編寫(xiě)(int x = 0; x < 5; x++),相反,可以省略int,保留空白。
通過(guò) Groovy 進(jìn)行循環(huán)
同大多數(shù)腳本語(yǔ)言一樣,Groovy 經(jīng)常被宣傳為生產(chǎn)力更高的 Java 語(yǔ)言替代品。
首先,用與創(chuàng)建HelloWorld相同的方式創(chuàng)建一個(gè) Groovy 類,并刪除自動(dòng)生成的類體:將要定義一個(gè)獨(dú)立的repeat函數(shù)。現(xiàn)在在控制臺(tái)中輸入以下代碼:
def ?repeat(val){
for(i = 0; i < 5; i++){
println val
}
}
起初,從 Java 的角度來(lái)看,這個(gè)小函數(shù)看起來(lái)可能有些怪(實(shí)際上,它很像 JavaScript)。但它就是 Java 代碼,只不過(guò)是用 Groovy 的樣式編寫(xiě)的。
repeat函數(shù)接受一個(gè)變量val。請(qǐng)注意參數(shù)不需要def。方法體本質(zhì)上就是一個(gè)for循環(huán)。
調(diào)用這個(gè)函數(shù)。
repeat("hello?world")
會(huì)輸出 “hello world” 五次。請(qǐng)注意,for循環(huán)中省略了int。沒(méi)有變量類型的for循環(huán)要比標(biāo)準(zhǔn)的 Java 代碼短些。現(xiàn)在看看如果在代碼里加入范圍會(huì)出現(xiàn)什么情況。
Groovy 中的范圍
范圍是一系列的值。例如 “0..4” 表明包含整數(shù) 0、1、2、3、4。Groovy 還支持排除范圍,“0..<4” 表示 0、1、2、3。還可以創(chuàng)建字符范圍:“a..e” 相當(dāng)于 a、b、c、d、e。“a..e的所有值。
循環(huán)范圍
范圍為循環(huán)帶來(lái)了很大的方便。例如,前面從 0 遞增到 4 的for循環(huán)如下所示:
for(i = 0; i < 5; i++)
范圍可以將這個(gè)for循環(huán)變得更簡(jiǎn)潔,更易閱讀:
def repeat(val){
for(i in 0..5){
println val
}
}
設(shè)置范圍
如果運(yùn)行這個(gè)示例,可能會(huì)注意到一個(gè)小問(wèn)題:“Hello World” 輸出了六次而不是五次。這個(gè)問(wèn)題有三種解決方法:
將包含的范圍限制到 4: ?for(i in 0..4)
從 1 而不是 0 開(kāi)始: ?for(i in 1..5)
將范圍由包含改為排除:for(i in 0..<5)
不論采用哪種方法,都會(huì)得到原來(lái)的效果 — 輸出 “Hello World” 五次。
默認(rèn)參數(shù)值
現(xiàn)在已經(jīng)成功地使用 Groovy 的范圍表達(dá)式縮短了repeat函數(shù)。但這個(gè)函數(shù)依然有些限制。如果想重復(fù) “Hello World” 八次該怎么辦?如果想對(duì)不同的值重復(fù)不同次數(shù) — 比如 “Hello World” 重復(fù)八次,“Goodbye Sunshine” 重復(fù)兩次,這時(shí)該怎么辦?
每次調(diào)用repeat時(shí)都要指定需要的重復(fù)次數(shù)的做法已經(jīng)過(guò)時(shí)了,特別是在已經(jīng)適應(yīng)了默認(rèn)行為(重復(fù)五次)的時(shí)候。
Groovy 支持默認(rèn)參數(shù)值,可以在函數(shù)或方法的正式定義中指定參數(shù)的默認(rèn)值。調(diào)用函數(shù)的程序可以選擇省略參數(shù),使用默認(rèn)值。
def repeat(val, repeat=5){
for(i in 0..repeat){
println val
}
}
像下面這樣調(diào)用該函數(shù):
repeat("Hello World", 2)
repeat("Goodbye sunshine", 4)
repeat("foo")
結(jié)果會(huì)輸出 “Hello World” 兩次,“Goodbye sunshine” 四次,“foo” 五次(默認(rèn)次數(shù))。
Groovy 集合
在 Groovy 提供的所有方便的快捷方式和功能中,最有幫助的一個(gè)可能就是內(nèi)置的集合。在 Java 編程中使用集合— 導(dǎo)入java.util類,初始化集合,將項(xiàng)加入集合。這三個(gè)步驟都會(huì)增加不少代碼。
而 Groovy 可以直接在語(yǔ)言內(nèi)使用集合。在 Groovy 中,不需要導(dǎo)入專門(mén)的類,也不需要初始化對(duì)象。集合是語(yǔ)言本身的本地成員。Groovy 也使集合(或者列表)的操作變得非常容易,為增加和刪除項(xiàng)提供了直觀的幫助。
def range = 0..4 ? ? //把范圍當(dāng)集合
println range.class
assert range instanceof List ? //證明了range是集合
Groovy 的集合支持相當(dāng)豐富,而且美妙之處就在于,在 Groovy 的魔法背后,一切都是標(biāo)準(zhǔn)的 Java 對(duì)象。每個(gè) Groovy 集合都是java.util.Collection或java.util.Map的實(shí)例。
def coll = ["Groovy", "Java", "Ruby"]
assert? coll instanceof Collection
assert coll instanceof ArrayList
你將會(huì)注意到,coll對(duì)象看起來(lái)很像 Java 語(yǔ)言中的數(shù)組。實(shí)際上,它是一個(gè)Collection。要在普通的 Java 代碼中得到集合的相同實(shí)例,必須執(zhí)行以下操作:
Collection coll = new ArrayList();
coll.add("Groovy");
coll.add("Java");
coll.add("Ruby");
在 Java 代碼中,必須使用add()方法向ArrayList實(shí)例添加項(xiàng)。
Groovy 提供了許多方法可以將項(xiàng)添加到列表 — 可以使用add()方法(因?yàn)榈讓拥募鲜且粋€(gè)普通的ArrayList類型),但是還有許多快捷方式可以使用。
例如:
coll.add("Python")
coll << "Smalltalk"
coll[5] = "Perl"
請(qǐng)注意,Groovy 支持操作符重載 —<<操作符被重載,以支持向集合添加項(xiàng)。還可以通過(guò)位置參數(shù)直接添加項(xiàng)。在這個(gè)示例中,由于集合中只有四個(gè)項(xiàng),所以[5]操作符將 “Perl” 放在最后。請(qǐng)自行輸出這個(gè)集合并查看效果。
Groovy 還允許在集合中增加或去掉集合,如下所示:
def numbers = [1,2,3,4]
assert numbers + 5 == [1,2,3,4,5]
assert numbers - [2,3] == [1,4]
在上面的代碼中, 實(shí)際上創(chuàng)建了新的集合實(shí)例,由最后一行可以看出。
魔法方法
Groovy 還為集合添加了其他一些方便的功能。例如,可以在集合實(shí)例上調(diào)用特殊的方法,如下所示:
def numbers = [1,2,3,4]
assert numbers.join(",") == "1,2,3,4"
assert [1,2,3,4,3].count(3) == 2
join()和count()只是在任何項(xiàng)列表上都可以調(diào)用的眾多方便方法中的兩個(gè)。分布操作符(spread operator)是個(gè)特別方便的工具,使用這個(gè)工具不用在集合上迭代,就能夠調(diào)用集合的每個(gè)項(xiàng)上的方法。
假設(shè)有一個(gè)String列表,現(xiàn)在想將列表中的項(xiàng)目全部變成大寫(xiě),可以編寫(xiě)以下代碼:
assert ["JAVA", "GROOVY"] ==["Java", "Groovy"]*.toUpperCase()
請(qǐng)注意*.標(biāo)記。對(duì)于以上列表中的每個(gè)值,都會(huì)調(diào)用toUpperCase(),生成的集合中每個(gè)String實(shí)例都是大寫(xiě)的。
Groovy 映射
除了豐富的列表處理功能,Groovy 還提供了堅(jiān)固的映射機(jī)制。同列表一樣,映射也是本地?cái)?shù)據(jù)結(jié)構(gòu)。而且 Groovy 中的任何映射機(jī)制在幕后都是java.util.Map的實(shí)例。
Java 語(yǔ)言中的映射是名稱-值對(duì)的集合。所以,要用 Java 代碼創(chuàng)建典型的映射,必須像下面這樣操作:
Map map = new HashMap();
map.put("name", "Andy");
map.put("VPN-#","45");
Groovy 使得處理映射的操作像處理列表一樣簡(jiǎn)單 — 例如,可以用 Groovy 將上面的 Java 映射寫(xiě)成
def hash = [name:"Andy", "VPN-#":45]
請(qǐng)注意,Groovy 映射中的鍵不必是String。在這個(gè)示例中,name看起來(lái)像一個(gè)變量,但是在幕后,Groovy 會(huì)將它變成String。
全都是 Java
接下來(lái)創(chuàng)建一個(gè)新類Mapper并加入上面的代碼。然后添加以下代碼,以證實(shí)底層的代碼是真正的 Java 代碼:
assert hash.getClass() == java.util.LinkedHashMap
可以看到 Groovy 使用了 Java 的LinkedHashMap類型,這意味著可以使用標(biāo)準(zhǔn)的 Java 一樣語(yǔ)句對(duì)hash中的項(xiàng)執(zhí)行put和get操作。
hash.put("id", 23)
assert hash.get("name") == "Andy"
有 groovy 特色的映射
hash.dob = "01/29/76"
.符號(hào)還可以用來(lái)獲取項(xiàng)。例如,使用以下方法可以獲取dob的值:
assert hash.dob == "01/29/76"
Groovy 中的閉包
不再需要更多迭代
雖然在前幾節(jié)編寫(xiě)了不少集合代碼,但還沒(méi)有實(shí)際地在集合上迭代。當(dāng)然,您知道 Groovy 就是 Java,所以如果愿意,那么總是能夠得到 Java 的Iterator實(shí)例,用它在集合上迭代,就像下面這樣:
def acoll = ["Groovy", "Java", "Ruby"]
for(Iterator iter = acoll.iterator(); iter.hasNext();){
println iter.next()
}
實(shí)際上在for循環(huán)中并不需要類型聲明,因?yàn)?Groovy 已經(jīng)將迭代轉(zhuǎn)變?yōu)槿魏渭系闹苯映蓡T。在這個(gè)示例中,不必獲取Iterator實(shí)例并直接操縱它,可以直接在集合上迭代。而且,通常放在循環(huán)構(gòu)造內(nèi)的行為(例如for循環(huán)體中println)接下來(lái)要放在閉包內(nèi)。在深入之前,先看看如何執(zhí)行這步操作。
能否看見(jiàn)閉包?
對(duì)于上面的代碼,可以用更簡(jiǎn)潔的方式對(duì)集合進(jìn)行迭代,如下所示:
def acoll = ["Groovy", "Java", "Ruby"]
acoll.each{
println it
}
請(qǐng)注意,each直接在acoll實(shí)例內(nèi)調(diào)用,而acoll實(shí)例的類型是ArrayList。在each調(diào)用之后,引入了一種新的語(yǔ)法 —{,然后是一些代碼,然后是}。由{}包圍起來(lái)的代碼塊就是閉包。
執(zhí)行代碼:
閉包中的it變量是一個(gè)關(guān)鍵字,指向被調(diào)用的外部集合的每個(gè)值 — 它是默認(rèn)值,可以用傳遞給閉包的參數(shù)覆蓋它。下面的代碼執(zhí)行同樣的操作,但使用自己的項(xiàng)變量:
def acoll = ["Groovy", "Java", "Ruby"]
acoll.each{ value ->
println value
}
在這個(gè)示例中,用value代替了 Groovy 的默認(rèn)it。
迭代無(wú)處不在
閉包在 Groovy 中頻繁出現(xiàn),但是,通常用于在一系列值上迭代的時(shí)候。請(qǐng)記住,一系列值可以用多種方式表示,不僅可以用列表表示 — 例如,可以在映射、String、JDBCRowset、File的行上迭代,等等。
對(duì)于前面的例子,可以編寫(xiě)以下代碼:
def hash = [name:"Andy", "VPN-#":45]
hash.each{ key, value ->
println "${key} : ${value}"
}
請(qǐng)注意,閉包還允許使用多個(gè)參數(shù) — 在這個(gè)示例中,上面的代碼包含兩個(gè)參數(shù)(key和value)。
使用 Java 代碼迭代
Mapmap = new HashMap();
map.put("name", "Andy");
map.put("VPN-#","45");
for(Iterator iter = map.entrySet().iterator(); iter.hasNext();){
Map.Entry entry = (Map.Entry)iter.next();
System.out.println(entry.getKey() + " : " + entry.getValue());
}
很明顯,上面的代碼比 Groovy 的代碼長(zhǎng)得多,如果要處理大量集合,那么顯然用 Groovy 處理會(huì)更方便。
閉包的更多使用方式
雖然在迭代上使用閉包的機(jī)會(huì)最多,但閉包確實(shí)還有其他用途。因?yàn)殚]包是一個(gè)代碼塊,所以能夠作為參數(shù)進(jìn)行傳遞(Groovy 中的函數(shù)或方法不能這樣做)。閉包在調(diào)用的時(shí)候才會(huì)執(zhí)行這一事實(shí)(不是在定義的時(shí)候)使得它們?cè)谀承﹫?chǎng)合上特別有用。
例如,通過(guò) Eclipse 創(chuàng)建一個(gè)ClosureExample對(duì)象,并保持它提供的默認(rèn)類語(yǔ)法。在生成的main()方法中,添加以下代碼:
def excite = { word ->
return "${word}!!"
}
這段代碼是名為excite的閉包。這個(gè)閉包接受一個(gè)參數(shù)(名為word),返回的String是word變量加兩個(gè)感嘆號(hào)。請(qǐng)注意在String實(shí)例中替換的用法。在String中使用${value}語(yǔ)法將告訴 Groovy 替換String中的某個(gè)變量的值。可以將這個(gè)語(yǔ)法當(dāng)成return word + "!!"的快捷方式。
延遲執(zhí)行
assert "Groovy!!" == excite("Groovy")
ssert "Java!!" == excite.call("Java")
可以看到,兩種調(diào)用方式都能工作,但是直接調(diào)用的方法更簡(jiǎn)潔。
文件I/O操作
直接來(lái)看例子吧,雖然比Java看起來(lái)簡(jiǎn)單,但要理解起來(lái)其實(shí)比較難。尤其是當(dāng)你要自己查SDK并編寫(xiě)代碼的時(shí)候。
整體說(shuō)來(lái),Groovy的I/O操作是在原有Java I/O操作上進(jìn)行了更為簡(jiǎn)單方便的封裝,并且使用Closure來(lái)簡(jiǎn)化代碼編寫(xiě)。主要封裝了如下一些了類:
讀文件
Groovy中,文件讀操作簡(jiǎn)單到令人發(fā)指:
def targetFile = new File(文件名) <==File對(duì)象還是要?jiǎng)?chuàng)建的。
讀該文件中的每一行:eachLine的唯一參數(shù)是一個(gè)Closure。Closure的參數(shù)是文件每一行的內(nèi)容
寫(xiě)文件:
結(jié)束語(yǔ)
通過(guò)本篇的學(xué)習(xí),進(jìn)一步認(rèn)識(shí)到 Groovy 就是 Java,只是缺少了過(guò)去使用Java的許多語(yǔ)法規(guī)則。Groovy 是沒(méi)有類型、沒(méi)有修改符、沒(méi)有 return、沒(méi)有Iterator、不需要導(dǎo)入集合的 Java。簡(jiǎn)而言之,Groovy 就是丟掉了許多包袱的 Java,這些包袱可能會(huì)壓垮 Java 項(xiàng)目。
但是在幕后,Groovy 就是 Java。