Android Gradle(一)——Groovy基礎
一、Groovy概述
Groovy是基於JVM的一種動態語言,它結合了Python、Ruby和Smalltalk的特性,同時能與Java代碼很好的結合,用於擴展現在的代碼,具有以下特點:
- 具有動態語言的特點,如動態類型轉換、閉包和元編程
- 面向對象編程,同時也可以作爲腳本語言
- 直接編譯成Java字節碼,在任何使用Java的地方都可以使用Groovy
- Groovy完全兼容Java語言,無縫集成所有Java已有的庫和對象
- 支持函數式編程,無需main函數
- 支持DSL,gradle自動化構建系統就是用Groovy語言實現的
二、Groovy語法
Groovy完全兼容Java,所以在Groovy文件中可以寫任何的Java代碼來進行編程,這裏主要是講Groovy不同於Java的語法特性(在Groovy中所有的代碼都不必要用;
結尾):
1.變量
Groovy具有動態類型轉換的特點,所以類型對於變量,屬性,方法,閉包參數以及函數的返回類型都是可有可無的,在給變量賦值時才確定它的類型,並且在需要時,很多類型之間的轉換都可以自動發生。這裏要注意的是Groovy可以直接編譯成class字節碼,JVM是無法判斷字節碼是Java生成的還是Groovy生成的。除了基本類型外,Groovy中的變量類型都是對象
下面是一些常見變量的聲明的例子
字符串
def str1 = '單引號' //Groovy中的字符串可以用單引號來表示
def str2 = "雙引號" //也可以用雙引號表示
Groovy中的單引號聲明的字符串中不具有運算能力,即不能包含運算表達式,而雙引號可以通過${str1}
的方式來進行運算
println '單引號的變量運算:${str1}'
println "雙引號的變量運算:${str2}" //只有雙引號才能運算
println "運算表達式結果爲:${str1+str2}" //${}中可以進行運算
輸出爲:
單引號的變量運算:${str1}
雙引號的變量運算:雙引號
運算表達式結果爲:單引號雙引號
集合
Groovy兼容了Java中的集合,並進行了擴展,如Groovy中的集合中的元素允許使用不同的類型。注意,在Groovy中定義的集合是Java中對應類的對象,可以使用任何Java庫提供的方法,如size(),add(E e)
等方法。這裏主要講常見的List和Map
List
Groovy中的List無需實現List接口,聲明如下:
def numList = [1,2,3,4,5,6]
def mixList = ['1','2',"start",4,5,6] //Groovy可以自動進行動態類型轉換
默認的List是一個ArrayList類型,比如我們可以通過如下方式查看它的類型:
println numList.getClass().name //輸出爲:java.util.ArrayList
Groovy訪問List的方式與Java不同,無需通過get方法,而是直接使用下標來進行訪問的:
println mixList //輸出整個List,爲:[1, 2, start, 4, 5, 6]
println numList[0] //訪問第一個元素
println numList[1] //訪問第二個元素
println numList[-1] //訪問倒數第一個元素
println numList[-2] //訪問倒數第二個元素
println numList[1..3] //訪問第二個到第四個元素,輸出爲:[2, 3, 4]
println numList[1..3].getClass().name //java.util.ArrayList
Groovy中List還提供了each方法傳入閉包來進行迭代操作
numList.each {
println it
}
輸出爲:
1
2
3
4
5
6
Map
Map的用法類似於List,區別只是Map是通過鍵值對的方式來聲明的:
def map = ['name':'Mike', 'age':20]
println map.getClass().name //java.util.LinkedHashMap
Groovy中的Map默認是LinkedHashMap類型,訪問可以通過map[key]
或者map.key
來進行訪問:
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.函數
函數與方法的區別在於函數式獨立於類外的,而方法是類中的方法,是一種特殊的函數,兩者基本語法相同,方法多了一些類的特性和訪問權限而已
Groovy中的函數與Java中的不同點如下:
- 可以使用def來定義,定義形參時可以不用顯示定義類型,Groovy會自動進行動態類型轉換
- 參數外的括號可以省略,函數名和參數列表之間用空格隔開,無參函數必須帶括號
- 函數嵌套在同一行調用時,只有第一個函數可以省略括號
- 無需return語句,Groovy會自動將函數中真正執行的最後一句代碼作爲其返回值,如果使用void修飾函數,函數會返回null(比如定義變量或者直接給出常量,就是有返回值,調用無返回值的函數,如println,就是無返回值)
例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 這是錯誤的寫法,會報錯
println fun1('abc','def') //輸出爲:abcdef
def fun1(a, b) {
a
b
a+b
}
3.類
在Groovy類中默認修飾符是public,沒有default修飾符。
Groovy會自動給類中的變量設置setter和getter方法(你當然可以自己手動重寫它們),我們可以直接通過類名.成員
來調用這些方法來對類中的成員變量進行訪問和修改。(請注意Groovy自動設置的setter和getter不能通過方法名去調用它們,如果你確實要這樣做,請在類中自定義這些方法)
Person p = new Person()
println "名字爲:${p.name}" //默認爲null,輸出爲:名字爲:null
println p.i //默認爲0,輸出爲:0
println p.b //默認爲false,輸出爲:false
p.name = 'Mike'
println "名字爲:${p.name}" //輸出爲:名字爲:Mike
class Person {
private String name
private boolean b
private int i
}
你也可以手動重寫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"
}
}
在類中可以直接通過成員名訪問從父類繼承來的私有成員,但是這種情況父類必須自己定義getter/setter方法
class C1 {
private int x = 5
//必須包含這個方法,不然子類無法直接使用x
int getX() {
return x
}
}
class C2 extends C1 {
public test() {
println x
}
}
new C2().test() //5
.
運算符並不一定是操作類的成員變量的,其實質只是調用了setter/getter方法而已:
User user = new User()
println user.age //輸出爲:12
//user.age = 10 User中沒有定義setAge方法,所以會報錯
class User {
public int getAge() {
12
}
}
在Gradle中有很多類似於這樣的用法,其實類中並沒有定義對應的屬性,只不過是定義了相應的getter/setter方法而已
三、Groovy閉包
閉包是Groovy非常重要的一個特性,也是DSL的基礎。閉包使Groovy省去了Java匿名內部類的繁瑣,使代碼變的靈活,輕量,以及可複用。
閉包實質就是可以用作函數參數和方法參數的代碼塊,可以將這個代碼塊理解爲一個函數指針
定義閉包
閉包在Groovy中是groov.lang.Closure類,可以用Closure來聲明,也可以用def來聲明
Groovy中閉包的定義如下:
def xxx = {[params -> ] code} //記住 -> 是連在一起的,不能分開
params可以理解爲是函數的形參,形參名不能和外部變量同名,如果只有一個參數,可以將不指定參數,Groovy會指定一個隱式的默認參數it,如果沒有參數,也可以不指定參數:
Closure closure1 = {
println it
}
Closure closure2 = {
k,v ->
println "${k} is ${v}"
}
調用閉包
閉包可以直接當做一段代碼來調用,通過閉包.call(參數)
或者閉包(參數)
來調用,也可以作爲參數傳遞到函數中在調用。閉包跟方法一樣,可以省略括號,用空格代替,當然無參閉包必須帶上括號,否則會輸出閉包的類對象
closure1.call('Mike') //Mike
closure2('name', 'Mike') //name is Mike
closure2 'name', 'Mike' //name is Mike
closure3.call() //There is no parameter
需要注意的是,閉包是可以訪問外部變量的,而函數不行
String name = 'Jack'
Closure closure4 = {
println name
}
closure4() //Jack
閉包跟函數一樣,是有返回值的,默認最後一行語句是閉包的返回值,如果最後一行語句沒有返回類型,那麼返回null(比如定義變量或者直接給出常量,就是有返回值)
//定義一個有返回值的閉包
Closure closure5 = {
'hello world'
}
//打印兩個閉包的返回值,closure4最後一句是沒有返回值的
println closure4() //null
println closure5() //hello world
閉包可以視爲一個普通的變量,所以閉包可以作爲參數傳遞給函數,或者另一個閉包,也可以作爲閉包的返回值返回
def run1 = {a -> a * a}
def run2 = {x, y -> y(x)}
def run3 = {m -> {n -> m * n}}
println run3(3)(run2(5,run1)) //輸出爲:75
分析上段代碼:
1. 閉包run3傳入了參數3,即m = 3,並返回一個閉包{n-> 3*n}
2. 返回的閉包繼續傳入參數run2(5,run1),即n = run2(5,run1),返回一個數值爲3*run2(5,run1)
3. 繼續看run2(5,run1),run2接受參數5和run1,返回值爲run1(5),即爲25
4. 所以結果爲75
對於閉包有一些省略的寫法:
- 當匿名閉包作爲參數時,如果它是最後一個參數,或者是唯一的參數,可以將閉包從括號中拉出來,放在括號後面:
//之前的代碼可以寫成如下形式:
println run3(3)(run2(5){a -> a * a}) //將run1從run2參數列表中拉出
再看下List的each方法的寫法:
numList.each {println it}
實質是List.each(closure),即真正寫法是這樣的:
numList.each({println it})
然後將閉包從參數列表中拉出:
numList.each(){println it}
在根據函數和閉包的參數寫法可以省略括號,用空格代替:
numList.each {println it}
閉包的參數可以接受Map和List:
- 閉包參數中與鍵值關係有關的參數,會自動封裝起來放到閉包的第一個參數
def x = {a, b, c -> a.x * a.y + b + c}
println x(5, 7, x:2, y:3) //18
- 如果閉包參數列表中本身沒有List,那麼傳入List會將List中的元素依次匹配到參數列表
def c = {a, b ,c -> a * b + c}
def list = [1,2,3]
println c(list) //5
閉包委託
委託策略是Groovy閉包中獨有的語法,Groovy通過閉包委託策略使得DSL語言更加優化和簡潔,在Gradle中就大量使用了閉包的委託策略。
在抽象類groovy.lang.Closure中包含了三個成員變量:
private Object delegate;
private Object owner;
private Object thisObject;
三個成員含義如下:
- thisObject指閉包所在的類,注意是類對象,並且是在閉包定義時的外圍類對象,匿名閉包的thisObject並不是其調用者
- owner指閉包上層的對象,即包含閉包的類或者閉包(多層嵌套的情況下)
- delegate默認爲owner
根據前面講的Groovy中類中成員的訪問方式,Closure是抽象類,其中定義了對應的getter,delegate還設置了對應的setter方法。我們定義的都是它的實現子類,可以在閉包中通過三種成員名來直接訪問這三種成員。
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策略
當閉包中出現一個屬性沒有指定其所有者時,就會執行對應的Delegate策略:
- OWNER_FIRST:這是默認的策略,會優先從owner中尋找對應的屬性,如果找不到會去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
上述代碼,如果在閉包中設置了name = 100
,那麼閉包調用時不會調用委託策略,而是直接輸出100
第一次輸出user是因爲默認從owner中找name屬性
第二次輸出person是因爲設置了優先從delegate中尋找name屬性
第三次輸出Wrapper是因爲匿名閉包的owner是外部的類,而非其調用者,所以輸出Wrapper
在Gradle中基本上都是用Delegate策略使用閉包來對項目進行配置屬性的
ps:第一次寫博客,請多多指教!