Android Gradle(一)——Groovy基礎

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:第一次寫博客,請多多指教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章