1. 程式人生 > >Gradle 理解 (二):Groovy 介紹

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

控制檯會輸出

  1. 不準吃零食
  2. 不準喝可樂
  3. 不準打豆豆

相當於來說,這幾種特性還是很有用的。只能說 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 領域特定語言,這個概念有點太高大上了,通俗來說就是“老司機才能懂的語言”。舉個簡單的例子

img

車上放水這種事情就是老司機(領域特定語言)才能懂的呀。也就是隻要你遵守某些特定規則,就能實現某些功能。你就往車上放水,就可能…其實 DSL 的例子還是很多的,很多你一直在接觸而沒注意,比如 Android 中的 XML 佈局,前端的 HTML,這些都是 DSL 。