一神帅、Groovy概述
Groovy是基于JVM的一種動(dòng)態(tài)語(yǔ)言罕容,它結(jié)合了Python忘渔、Ruby和Smalltalk的特性高帖,同時(shí)能與Java代碼很好的結(jié)合,用于擴(kuò)展現(xiàn)在的代碼畦粮,具有以下特點(diǎn):
- 具有動(dòng)態(tài)語(yǔ)言的特點(diǎn)散址,如動(dòng)態(tài)類型轉(zhuǎn)換、閉包和元編程
- 面向?qū)ο缶幊绦猓瑫r(shí)也可以作為腳本語(yǔ)言
- 直接編譯成Java字節(jié)碼预麸,在任何使用Java的地方都可以使用Groovy
- Groovy完全兼容Java語(yǔ)言,無(wú)縫集成所有Java已有的庫(kù)和對(duì)象
- 支持函數(shù)式編程儒将,無(wú)需main函數(shù)
- 支持DSL吏祸,gradle自動(dòng)化構(gòu)建系統(tǒng)就是用Groovy語(yǔ)言實(shí)現(xiàn)的
二、Groovy語(yǔ)法
Groovy完全兼容Java钩蚊,所以在Groovy文件中可以寫(xiě)任何的Java代碼來(lái)進(jìn)行編程贡翘,這里主要是講Groovy不同于Java的語(yǔ)法特性(在Groovy中所有的代碼都不必要用;
結(jié)尾):
1.變量
Groovy具有動(dòng)態(tài)類型轉(zhuǎn)換的特點(diǎn),所以類型對(duì)于變量砰逻,屬性鸣驱,方法,閉包參數(shù)以及函數(shù)的返回類型都是可有可無(wú)的诱渤,在給變量賦值時(shí)才確定它的類型丐巫,并且在需要時(shí),很多類型之間的轉(zhuǎn)換都可以自動(dòng)發(fā)生勺美。這里要注意的是Groovy可以直接編譯成class字節(jié)碼递胧,JVM是無(wú)法判斷字節(jié)碼是Java生成的還是Groovy生成的。除了基本類型外赡茸,Groovy中的變量類型都是對(duì)象
下面是一些常見(jiàn)變量的聲明的例子
字符串
def str1 = '單引號(hào)' //Groovy中的字符串可以用單引號(hào)來(lái)表示
def str2 = "雙引號(hào)" //也可以用雙引號(hào)表示
Groovy中的單引號(hào)聲明的字符串中不具有運(yùn)算能力缎脾,即不能包含運(yùn)算表達(dá)式,而雙引號(hào)可以通過(guò)${str1}
的方式來(lái)進(jìn)行運(yùn)算
println '單引號(hào)的變量運(yùn)算:${str1}'
println "雙引號(hào)的變量運(yùn)算:${str2}" //只有雙引號(hào)才能運(yùn)算
println "運(yùn)算表達(dá)式結(jié)果為:${str1+str2}" //${}中可以進(jìn)行運(yùn)算
輸出為:
單引號(hào)的變量運(yùn)算:${str1}
雙引號(hào)的變量運(yùn)算:雙引號(hào)
運(yùn)算表達(dá)式結(jié)果為:單引號(hào)雙引號(hào)
集合
Groovy兼容了Java中的集合占卧,并進(jìn)行了擴(kuò)展遗菠,如Groovy中的集合中的元素允許使用不同的類型联喘。注意,在Groovy中定義的集合是Java中對(duì)應(yīng)類的對(duì)象辙纬,可以使用任何Java庫(kù)提供的方法豁遭,如size(),add(E e)
等方法。這里主要講常見(jiàn)的List和Map
List
Groovy中的List無(wú)需實(shí)現(xiàn)List接口贺拣,聲明如下:
def numList = [1,2,3,4,5,6]
def mixList = ['1','2',"start",4,5,6] //Groovy可以自動(dòng)進(jìn)行動(dòng)態(tài)類型轉(zhuǎn)換
默認(rèn)的List是一個(gè)ArrayList類型蓖谢,比如我們可以通過(guò)如下方式查看它的類型:
println numList.getClass().name //輸出為:java.util.ArrayList
Groovy訪問(wèn)List的方式與Java不同,無(wú)需通過(guò)get方法譬涡,而是直接使用下標(biāo)來(lái)進(jìn)行訪問(wèn)的:
println mixList //輸出整個(gè)List闪幽,為:[1, 2, start, 4, 5, 6]
println numList[0] //訪問(wèn)第一個(gè)元素
println numList[1] //訪問(wèn)第二個(gè)元素
println numList[-1] //訪問(wèn)倒數(shù)第一個(gè)元素
println numList[-2] //訪問(wèn)倒數(shù)第二個(gè)元素
println numList[1..3] //訪問(wèn)第二個(gè)到第四個(gè)元素,輸出為:[2, 3, 4]
println numList[1..3].getClass().name //java.util.ArrayList
Groovy中List還提供了each方法傳入閉包來(lái)進(jìn)行迭代操作
numList.each {
println it
}
輸出為:
1
2
3
4
5
6
Map
Map的用法類似于List涡匀,區(qū)別只是Map是通過(guò)鍵值對(duì)的方式來(lái)聲明的:
def map = ['name':'Mike', 'age':20]
println map.getClass().name //java.util.LinkedHashMap
Groovy中的Map默認(rèn)是LinkedHashMap類型盯腌,訪問(wèn)可以通過(guò)map[key]
或者map.key
來(lái)進(jìn)行訪問(wèn):
println map['name'] //輸出為:Mike
println map.age //輸出為:20
Map也有與List類似的迭代操作:
map.each {
println "Key:${it.key}, Value:${it.value}"
}
輸出為:
Key:name, Value:Mike
Key:age, Value:20
2.函數(shù)
函數(shù)與方法的區(qū)別在于函數(shù)式獨(dú)立于類外的,而方法是類中的方法陨瘩,是一種特殊的函數(shù)腕够,兩者基本語(yǔ)法相同,方法多了一些類的特性和訪問(wèn)權(quán)限而已
Groovy中的函數(shù)與Java中的不同點(diǎn)如下:
- 可以使用def來(lái)定義舌劳,定義形參時(shí)可以不用顯示定義類型燕少,Groovy會(huì)自動(dòng)進(jìn)行動(dòng)態(tài)類型轉(zhuǎn)換
- 參數(shù)外的括號(hào)可以省略,函數(shù)名和參數(shù)列表之間用空格隔開(kāi)蒿囤,無(wú)參函數(shù)必須帶括號(hào)
- 函數(shù)嵌套在同一行調(diào)用時(shí),只有第一個(gè)函數(shù)可以省略括號(hào)
- 無(wú)需return語(yǔ)句崇决,Groovy會(huì)自動(dòng)將函數(shù)中真正執(zhí)行的最后一句代碼作為其返回值材诽,如果使用void修飾函數(shù),函數(shù)會(huì)返回null(比如定義變量或者直接給出常量恒傻,就是有返回值脸侥,調(diào)用無(wú)返回值的函數(shù),如println盈厘,就是無(wú)返回值)
例1:
fun1(10, 11) //輸出為:21
fun1 8,9 //輸出為:17
def fun1(int a, int b) {
println a+b
}
例2:
println fun1(10, 11) //輸出為:21
//println fun1 8,9 這是錯(cuò)誤的寫(xiě)法睁枕,會(huì)報(bào)錯(cuò)
println fun1('abc','def') //輸出為:abcdef
def fun1(a, b) {
a
b
a+b
}
3.類
在Groovy類中默認(rèn)修飾符是public,沒(méi)有default修飾符沸手。
Groovy會(huì)自動(dòng)給類中的變量設(shè)置setter和getter方法(你當(dāng)然可以自己手動(dòng)重寫(xiě)它們)外遇,我們可以直接通過(guò)類名.成員
來(lái)調(diào)用這些方法來(lái)對(duì)類中的成員變量進(jìn)行訪問(wèn)和修改。(請(qǐng)注意Groovy自動(dòng)設(shè)置的setter和getter不能通過(guò)方法名去調(diào)用它們契吉,如果你確實(shí)要這樣做跳仿,請(qǐng)?jiān)陬愔凶远x這些方法)
Person p = new Person()
println "名字為:${p.name}" //默認(rèn)為null,輸出為:名字為:null
println p.i //默認(rèn)為0捐晶,輸出為:0
println p.b //默認(rèn)為false菲语,輸出為:false
p.name = 'Mike'
println "名字為:${p.name}" //輸出為:名字為:Mike
class Person {
private String name
private boolean b
private int i
}
你也可以手動(dòng)重寫(xiě)getter方法:
Person p = new Person()
println "名字為:${p.name}" //輸出為:名字為:abc
p.name = 'Mike'
println "名字為:${p.name}" //輸出為:名字為:abc
class Person {
private String name
public String getName() {
return "abc"
}
}
在類中可以直接通過(guò)成員名訪問(wèn)從父類繼承來(lái)的私有成員妄辩,但是這種情況父類必須自己定義getter/setter方法
class C1 {
private int x = 5
//必須包含這個(gè)方法,不然子類無(wú)法直接使用x
int getX() {
return x
}
}
class C2 extends C1 {
public test() {
println x
}
}
new C2().test() //5
.
運(yùn)算符并不一定是操作類的成員變量的山上,其實(shí)質(zhì)只是調(diào)用了setter/getter方法而已:
User user = new User()
println user.age //輸出為:12
//user.age = 10 User中沒(méi)有定義setAge方法眼耀,所以會(huì)報(bào)錯(cuò)
class User {
public int getAge() {
12
}
}
在Gradle中有很多類似于這樣的用法,其實(shí)類中并沒(méi)有定義對(duì)應(yīng)的屬性佩憾,只不過(guò)是定義了相應(yīng)的getter/setter方法而已
三哮伟、Groovy閉包
閉包是Groovy非常重要的一個(gè)特性,也是DSL的基礎(chǔ)鸯屿。閉包使Groovy省去了Java匿名內(nèi)部類的繁瑣澈吨,使代碼變的靈活,輕量寄摆,以及可復(fù)用谅辣。
<font face="微軟雅黑" size = 4>閉包實(shí)質(zhì)就是可以用作函數(shù)參數(shù)和方法參數(shù)的代碼塊,可以將這個(gè)代碼塊理解為一個(gè)函數(shù)指針</font>
定義閉包
閉包在Groovy中是groov.lang.Closure類婶恼,可以用Closure來(lái)聲明桑阶,也可以用def來(lái)聲明
Groovy中閉包的定義如下:
def xxx = {[params -> ] code} //記住 -> 是連在一起的,不能分開(kāi)
params可以理解為是函數(shù)的形參勾邦,形參名不能和外部變量同名蚣录,如果只有一個(gè)參數(shù),可以將不指定參數(shù)眷篇,Groovy會(huì)指定一個(gè)隱式的默認(rèn)參數(shù)it萎河,如果沒(méi)有參數(shù),也可以不指定參數(shù):
Closure closure1 = {
println it
}
Closure closure2 = {
k,v ->
println "${k} is ${v}"
}
調(diào)用閉包
閉包可以直接當(dāng)做一段代碼來(lái)調(diào)用蕉饼,通過(guò)閉包.call(參數(shù))
或者閉包(參數(shù))
來(lái)調(diào)用虐杯,也可以作為參數(shù)傳遞到函數(shù)中在調(diào)用。閉包跟方法一樣昧港,可以省略括號(hào)擎椰,用空格代替,當(dāng)然無(wú)參閉包必須帶上括號(hào)创肥,否則會(huì)輸出閉包的類對(duì)象
closure1.call('Mike') //Mike
closure2('name', 'Mike') //name is Mike
closure2 'name', 'Mike' //name is Mike
closure3.call() //There is no parameter
需要注意的是达舒,閉包是可以訪問(wèn)外部變量的,而函數(shù)不行
String name = 'Jack'
Closure closure4 = {
println name
}
closure4() //Jack
閉包跟函數(shù)一樣叹侄,是有返回值的巩搏,默認(rèn)最后一行語(yǔ)句是閉包的返回值,如果最后一行語(yǔ)句沒(méi)有返回類型趾代,那么返回null(比如定義變量或者直接給出常量塔猾,就是有返回值)
//定義一個(gè)有返回值的閉包
Closure closure5 = {
'hello world'
}
//打印兩個(gè)閉包的返回值,closure4最后一句是沒(méi)有返回值的
println closure4() //null
println closure5() //hello world
閉包可以視為一個(gè)普通的變量,所以閉包可以作為參數(shù)傳遞給函數(shù)稽坤,或者另一個(gè)閉包丈甸,也可以作為閉包的返回值返回
def run1 = {a -> a * a}
def run2 = {x, y -> y(x)}
def run3 = {m -> {n -> m * n}}
println run3(3)(run2(5,run1)) //輸出為:75
分析上段代碼:
- 閉包run3傳入了參數(shù)3糯俗,即m = 3,并返回一個(gè)閉包
{n-> 3*n}
- 返回的閉包繼續(xù)傳入?yún)?shù)run2(5,run1)睦擂,即n = run2(5,run1)得湘,返回一個(gè)數(shù)值為3*run2(5,run1)
- 繼續(xù)看run2(5,run1),run2接受參數(shù)5和run1顿仇,返回值為run1(5)淘正,即為25
- 所以結(jié)果為75
對(duì)于閉包有一些省略的寫(xiě)法:
- 當(dāng)匿名閉包作為參數(shù)時(shí),如果它是最后一個(gè)參數(shù)臼闻,或者是唯一的參數(shù)鸿吆,可以將閉包從括號(hào)中拉出來(lái),放在括號(hào)后面:
//之前的代碼可以寫(xiě)成如下形式:
println run3(3)(run2(5){a -> a * a}) //將run1從run2參數(shù)列表中拉出
再看下List的each方法的寫(xiě)法:
numList.each {println it}
實(shí)質(zhì)是List.each(closure)述呐,即真正寫(xiě)法是這樣的:
numList.each({println it})
然后將閉包從參數(shù)列表中拉出:
numList.each(){println it}
在根據(jù)函數(shù)和閉包的參數(shù)寫(xiě)法可以省略括號(hào)惩淳,用空格代替:
numList.each {println it}
閉包的參數(shù)可以接受Map和List:
- 閉包參數(shù)中與鍵值關(guān)系有關(guān)的參數(shù),會(huì)自動(dòng)封裝起來(lái)放到閉包的第一個(gè)參數(shù)
def x = {a, b, c -> a.x * a.y + b + c}
println x(5, 7, x:2, y:3) //18
- 如果閉包參數(shù)列表中本身沒(méi)有List乓搬,那么傳入List會(huì)將List中的元素依次匹配到參數(shù)列表
def c = {a, b ,c -> a * b + c}
def list = [1,2,3]
println c(list) //5
閉包委托
委托策略是Groovy閉包中獨(dú)有的語(yǔ)法思犁,Groovy通過(guò)閉包委托策略使得DSL語(yǔ)言更加優(yōu)化和簡(jiǎn)潔,在Gradle中就大量使用了閉包的委托策略进肯。
在抽象類groovy.lang.Closure中包含了三個(gè)成員變量:
private Object delegate;
private Object owner;
private Object thisObject;
三個(gè)成員含義如下:
- thisObject指閉包所在的類激蹲,注意是類對(duì)象,并且是在閉包定義時(shí)的外圍類對(duì)象江掩,匿名閉包的thisObject并不是其調(diào)用者
- owner指閉包上層的對(duì)象学辱,即包含閉包的類或者閉包(多層嵌套的情況下)
- delegate默認(rèn)為owner
根據(jù)前面講的Groovy中類中成員的訪問(wèn)方式,Closure是抽象類环形,其中定義了對(duì)應(yīng)的getter项郊,delegate還設(shè)置了對(duì)應(yīng)的setter方法。我們定義的都是它的實(shí)現(xiàn)子類斟赚,可以在閉包中通過(guò)三種成員名來(lái)直接訪問(wèn)這三種成員。
class C1 {
def firstClosure = {
println "firstClosure.thisObject:${thisObject.getClass()}"
println "firstClosure.owner:${owner.getClass()}"
println "firstClosure.delegate:${delegate.getClass()}"
def secondClosure = {
println "secondClosure.thisObject:${thisObject.getClass()}"
println "secondClosure.owner:${owner.getClass()}"
println "secondClosure.delegate:${delegate.getClass()}"
}
secondClosure()
new C2().test {
println "test.thisObject:${thisObject.getClass()}"
println "test.owner:${owner.getClass()}"
println "test.delegate:${delegate.getClass()}"
}
}
}
class C2 {
def test(Closure closure) {
closure()
}
}
new C1().firstClosure()
輸出如下:
firstClosure.thisObject:class C1
firstClosure.owner:class C1
firstClosure.delegate:class C1
secondClosure.thisObject:class C1
secondClosure.owner:class C1$_closure1
secondClosure.delegate:class C1$_closure1
test.thisObject:class C1
test.owner:class C1$_closure1
test.delegate:class C1$_closure1
Delegate策略
當(dāng)閉包中出現(xiàn)一個(gè)屬性沒(méi)有指定其所有者時(shí)差油,就會(huì)執(zhí)行對(duì)應(yīng)的Delegate策略:
- OWNER_FIRST:這是默認(rèn)的策略拗军,會(huì)優(yōu)先從owner中尋找對(duì)應(yīng)的屬性,如果找不到會(huì)去delegate中找
- DELEGATE_FIRST:與OWNER_FIRST相反蓄喇。
- OWNER_ONLY:只在owner中尋找
- DELEGATE_ONLY:只在delegate中尋找
- TO_SELF:只在閉包自身中找
看如下的例子:
class User {
String name
def c1 = {
//def name = 100
println name
}
}
class Person {
String name
def test(Closure c) {
c()
}
}
String name = 'Wrapper'
User u = new User()
Person p = new Person()
u.name = 'user'
p.name = 'person'
p.test(u.c1) //user
u.c1.setResolveStrategy(Closure.DELEGATE_FIRST)
u.c1.delegate = p
p.test(u.c1) //person
p.test {
println name
} //Wrapper
上述代碼发侵,如果在閉包中設(shè)置了name = 100
,那么閉包調(diào)用時(shí)不會(huì)調(diào)用委托策略妆偏,而是直接輸出100
第一次輸出user是因?yàn)槟J(rèn)從owner中找name屬性
第二次輸出person是因?yàn)樵O(shè)置了優(yōu)先從delegate中尋找name屬性
第三次輸出Wrapper是因?yàn)槟涿]包的owner是外部的類刃鳄,而非其調(diào)用者,所以輸出Wrapper
在Gradle中基本上都是用Delegate策略使用閉包來(lái)對(duì)項(xiàng)目進(jìn)行配置屬性的
ps:第一次寫(xiě)博客钱骂,請(qǐng)多多指教叔锐!