Gradle 理解 (二):Groovy 介紹
Groovy 是基於 JVM 的一門動態程式語言,語法跟 Java 很有很多相似之處,如果 Java 程式設計師或者 Android 程式設計師很容易就上手了。Groovy 在 Java 的基礎上又新增很多高階特性,比如說閉包。這很大從程度上提高的了 Groovy 的靈活性,也可以說 Groovy 是一門靈活的動態指令碼語言。這裡就大致介紹一下 Groovy 的部分語法。
1. 字串
Groovy 對字串的操作跟Java的操作有相似的地方,但是比 Java 的字串更加高階,更加方便。Groovy 中有三種字串,分別是單引號‘ ’,雙引號” “,跟三引號’’’ ‘’’。
1.1 單引號‘ ’
Groovy 的單引號跟 Java 的字串是一樣的。就是純粹的字串變數。編寫下面程式碼
task helloString {
doLast {
println ' hello Groovy String'
}
}
在終端執行:
gradle helloString
控制檯會輸出
hello Groovy String
1.2 雙引號” “
Groovy 的雙引號字串在單引號的基礎上有可以對錶達式做運算的作用。看個例子:
task helloString { def time=1 doLast { println " hello Groovy String ${time}" println " 1加上2等於: ${1+2}" } }
在終端執行:
gradle helloString
控制檯會輸出
hello Groovy String 1
1加上2等於: 3
從上面的例子可以看出,雙引號是有對錶達式做運算的功能的。這個功能相對於 Java 來說,也可以避免很多噁心的+號串聯字串。
1.3 三引號’’’ ‘’’
三引號字串支援隨意換行,原樣輸出字元原型。看個例子:
task helloString { def time=1 doLast { println ''' 1. 不準吃零食 2. 不準喝可樂 3. 不準打豆豆 ''' } }
在終端執行:
gradle helloString
控制檯會輸出
- 不準吃零食
- 不準喝可樂
- 不準打豆豆
相當於來說,這幾種特性還是很有用的。只能說 Groovy 對字串支援相當強大,綜合了一些指令碼語言的特性。類似 Kotlin 的字串也是跟 Groovy 的字串特性一樣。
2. 集合
Groovy 也是很強大。Groovy 不僅完全相容了 Java 集合,並且進行擴充套件。這裡我們可以學習一下常用兩種集合類 List 跟 Map
2.1 List類
如果你把它做成 Java 中的 List 語法操作也是可以的。比如:
task helloList{
List<String> list=new ArrayList<String>()
list.add("1")
list.add("2")
list.add("3")
list.add("4")
doLast {
for (int i = 0; i < list.size(); i++) {
print list.get(i)
}
}
}
在終端執行:
gradle helloList
控制檯會輸出
1234
當然,既然 Groovy 進行強大的功能擴充套件,我們也是要好好了解的。
在 Groovy 中定義一個 List 是非常方便的。
def list=[0,1,2,3]
這樣就定義了一個 ArrayList.定義好之後,訪問也是非常方便的。
list[0] //訪問第一個元素
list[-1] //訪問倒數最後一個元素
list[1…2] //訪問第二個跟第三個元素
而且都不用擔心索引越界,因為如果當前索引超過當前列表長度,列表會自動
往該索引新增元素
list[10086] //返回null
除此之外,列表元素裡面可以是任何物件
def list=[“MVP”,1,false]
還有一個很方便的簡化功能:迭代。這個比 Java 就方便太多了。
list.each{
println "list :${it} "
}
2.2 Map 類
同樣的道理,你把它做成 Java 中的 Map 語法操作。舉個例子:
task helloMap{
Map<String,String> map=new HashMap<>()
map.put("name","張三")
map.put("age","18")
map.put("address","深圳")
doLast {
println map.get("name")
println map.get("age")
println map.get("address")
}
}
在終端執行:
gradle helloMap
控制檯會輸出
張三
18
深圳
它的擴充套件功能跟List 拓展功能很相似:
簡潔的中定義方式
def map=[“name”:“張三”,“age”:“18”]
通過 key 超級方便的取值方式
println map.age
println map[‘age’]
簡單方便的迭代
map.each{
println "key :${it.key}===value :${it.value} "
}
3. 函式
Groovy 的函式比 Java 的函式新增更多靈活方便的特性。
3.1 括號可以省略
在 Java 中呼叫一個函式,一般都是 xxxxMethod(parm1,parm2)。非常的嚴謹跟傳統。但是在 Groovy 就方便很多,可以直接把括號給去掉。看個簡單例子:
task helloMethod{
doLast{
addNumber 1,2
}
}
def addNumber(int a,int b){
println "兩數相加:${a+b}"
}
看到沒有,呼叫 addNumber 的函式的時候,直接省略去了括號。不要小看這個簡單的特性,它在用於定義 DSL 的就飛鏟方便了。
3.2 返回值的時候可以省去 return
Groovy 函式支援不用寫返回值,直接用最後的執行程式碼做為返回值。
task helloReturn{
doLast{
def a = addNumber2 1,2
println "兩數相加:${a}"
}
}
def addNumber2(int a,int b){
a+b
}
addNumber2
這裡是沒有寫 return 的,但是在呼叫的時候接收到這裡的返回值,這裡把最後一行 a+b
當做返回值直接返回回來了。
3.3 程式碼塊可以當做引數傳遞
程式碼塊可以當做引數傳遞,也就後面要說的閉包。這個特性可以說是非常強大了。因為在 Java 中是不能做到這一個功能的,但是這一功能不僅會使程式碼變得易讀,優雅,而且還能為開發者提供很大的便利。在 Java 你想實現傳遞一段程式碼塊,只能建立一個實體類,然後讓實體類新增一個函式實現這個程式碼塊,用函式呼叫傳遞這個實體類,再讓函式引數呼叫這個實體類函式,這樣是非常不方便的。
讓我們以 List 集合為例:
task helloClosure {
def list = ["MVP", 1, false]
doLast {
list.each({ println "list :${it} " })
}
}
我們這裡呼叫 List裡面迭代函式,這個迭代函式裡面就支援傳遞一段程式碼塊,在遍歷到時候會執行這段程式碼塊。
當然,這樣看還體現不了易讀,優雅的特性。我們再演化一下:
在 Groovy 的語法中,如果引數最後一個是閉包,也就是程式碼塊的話,可以把程式碼塊放到函式外面去。
task helloClosure {
def list = ["MVP", 1, false]
doLast {
list.each() {
println "list :${it} "
}
}
}
如果如果閉包是唯一的一個引數的話,函式的括號還可以省略:
就演化成下面這樣了:
task helloClosure {
def list = ["MVP", 1, false]
doLast {
list.each{
println "list :${it} "
}
}
}
通過以上的函式的演變,應該就很容易瞭解 Groovy 的函式呼叫了。
4. JavaBean
Groovy 的實體主要改善了 Java 的訪問賦值的冗餘操作。在 Java 中為了體現封裝性,我們都會為每個屬性新增 set 跟 get 方法。在這一點上,Groovy 幫我從這種繁瑣中解脫出來。
task helloBean {
Person person = new Person()
println "小明的年齡是:${person.age}"
}
class Person {
private int age
}
看到沒有,這裡的 Person 的 age 是私有屬性,但是我們可以直接呼叫 person.age ,這裡並不是私有屬性不生效,而是 Groovy 預設幫我們實現了 set 跟 get 方法。當你呼叫 person.age 的時候其實就是相當於 Java 中呼叫 getAge() 方法。我們當然還可以重新定義 get 方法或者 set 方法。以 get 方法為例:
task helloBean {
Person person = new Person()
println "小明的年齡是:${person.age}"
}
class Person {
private int age
public int getAge(){
18
}
}
終端輸出:
小明的年齡是:18
5. 閉包
閉包這個概念在 Java 是中沒有的,但是它在其他程式語言中還是很有地位的。閉包作為 Groovy 中一個重要的特性,使程式碼變得更加靈活,方便,複用性強,同時也是 DSL 的基礎。我認為閉包的概念還是很重要的,所以這裡我會更加詳細的講解閉包的知識。
5.1 閉包語法
閉包其實也是一個物件 Closure
,可以在函式中傳遞,也可以初始化賦值。
-
定義閉包
{ [closureParameters -> ] statements }
[]
內是可選的閉包引數,可省略。當閉包帶有引數,就需要->
來將引數和閉包體相分離。 下面看下簡單示例Closure c = { age++ } Closure c = { -> age++ } Closure c = { println it } Closure c = { it -> println it } Closure c = { name -> println name } Closure callback = { int one, int two -> println "兩數相加:${one+two}"" }
-
呼叫閉包
呼叫閉包有兩種方式
Closure c = { age++ } c() //第一種方式 c.call() //第二種方式
-
閉包引數
呼叫閉包傳遞引數跟普通方法也是很相似的。都是在括號裡面傳遞引數,然後在程式碼塊裡面接受這個引數進行使用。
task helloClosure3{ doLast{ printlnAge{ age-> println "我的年齡是${age}" } } } def printlnAge(Closure helloClosure){ for(int i in 18..25){ helloClosure(i) } }
閉包裡面還有一個隱含引數:
it
。當閉包沒有顯式宣告引數時,其預設包含一個隱含的引數it
。task helloClosure4{ doLast{ printlnAge{ println "我的年齡是${it}" } } } def printlnAge(Closure helloClosure){ for(int i in 18..25){ helloClosure(i) } }
在這裡,閉包定義的時候並沒有包含 it 引數但是在使用這個it引數接受傳遞過來的引數。
看一個簡單的例子:
task helloClosure {
doLast {
numberEach {
println it
}
}
}
def numberEach(Closure helloClosure) {
for (int i in 1..10) {
helloClosure(i)
}
}
這段程式碼應該很好理解,首先定義一個numberEach
的函式,然後函式需要一個Closure
型別的引數,這個Closure
就是閉包。而閉包的呼叫方式就是 closure.call()
5.1 委託策略
委託策略是 Groovy 閉包中獨有的特性。
委託策略跟 Java 中的 this 是很相似,只是在 Java 中的 this 中又延伸擴充套件其他兩個概念。
主要有三個核心物件:this,owner和delegate。下面我舉例個場景,你就大致明白這裡面的細節了。
我們分別列三個物件,Country (國家),Home(家),Wife(妻子)
class Country {
String tag = "國家"
void run() {
def c={
println "this 效忠的是" + this
println "owner 效忠的是" + owner
println "delegate 效忠的是" + delegate
}
c()
}
}
task helloClosure5{
doLast{
def home=new Country()
home.run()
}
}
輸出的結果是:
this 效忠的是Country@4ca3cbda
owner 效忠的是Country@4ca3cbda
delegate 效忠的是Country@4ca3cbda
這種情況看來是三者是一模一樣的,這三個物件都是效忠國家的。再看一個場景
class Country {
String tag = "國家"
void run() {
def home={
def c={
println "this 效忠的是" + this
println "owner 效忠的是" + owner
println "delegate 效忠的是" + delegate
}
c()
}
println "閉包home的地址是" + home.toString()
home.toString()
}
}
我這裡主要改動點是,在c 閉包中再新增一層閉包 home,並且打印出閉包 home 中的地址。
最後輸出:
閉包home的地址是Country$_run_closure1@7e00a573
this 效忠的是Country@f2726d1
owner 效忠的是Country$_run_closure1@7e00a573
delegate 效忠的是Country$_run_closure1@7e00a573
這裡看看出,this 還是指向的是外層類 Country ,但是 owner 跟 delegate 指向的是閉包裡面的物件。
也就是除了 this,其它兩個已經不效忠國家了,而是效忠自己的家庭(該閉包的外層閉包物件)。
下面再看一個關於 delegate 的例子:
class Country {
String tag = "國家"
def home={
println "this 效忠的是" + this
println "owner 效忠的是" + owner
println "delegate 效忠的是" + delegate
println "delegate 的名字是" + delegate.name
}
void run() {
home()
}
}
class Wife{
def name="蘇珊"
@Override
String toString() {
return "妻子"
}
}
task helloClosure5{
doLast{
def country=new Country()
country.home.delegate=new Wife()
country.run()
}
}
我們主要改動點在於把 home 的 delegate 賦值一個物件 Wife。
最終輸出:
this 效忠的是Country@72641004
owner 效忠的是Country@72641004
delegate 效忠的是妻子
delegate 的名字是蘇珊
耶,這裡看到 delegate 效忠物件變成了妻子,這裡看到 delegate 其實是一個動態的可以隨便更改複製的代理物件,同時可以使用代理物件的屬性。
這裡就可以總結歸納了:
this 物件無論什麼情況都是指向類。(至始至終以國家為大任)
owner 物件在外層是閉包的情況下,指向的是閉包,其他情況跟 this 是一樣的。(如果沒有成家,國家大事為重,如果有家了,就以家事為重)
delegate 沒有賦予值的情況下,跟 owner 一樣,但是如果賦值了,就是代表賦值的物件。(娶到什麼樣的妻,就代表該妻子)。
DSL
DSL 領域特定語言,這個概念有點太高大上了,通俗來說就是“老司機才能懂的語言”。舉個簡單的例子
車上放水這種事情就是老司機(領域特定語言)才能懂的呀。也就是隻要你遵守某些特定規則,就能實現某些功能。你就往車上放水,就可能…其實 DSL 的例子還是很多的,很多你一直在接觸而沒注意,比如 Android 中的 XML 佈局,前端的 HTML,這些都是 DSL 。