Gradle 入門之 Groovy 語言詳解

Gradle 核心是基於 Groovy 腳本語言,Groovy 腳本基於 Java 且拓展了 Java。因此 Gradle 需要依賴 JDK 和 Groovy 庫。

快速安裝 Groovy 可以通過 Bash,命令如下:

$ curl -s get.sdkman.io | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk install groovy
// 查看版本,判斷是否成功
$ groovy -version

關鍵字:

as、assert、break、case、catch、class、const、continue、def、default、do、else、enum、extends、false、finally、for、goto、if、implements、import、in、instanceof、interface、new、null、package、return、super、switch、this、throw、throws、trait、true、try、while

Hello Groovy

#!/usr/bin/env groovy
println "Hello Groovy."

一、類型定義

1,標識符

  • 普通標識符:只能以字母、美元符、下劃線開始,不能以數字開頭。
  • 引用標識符:引用標識符出現在點後的表達式中。

    // 定義一個空的 map 集合
    def map = [:]
    // 引用標示符中可以出現空格、橫杆等
    map."a b-c" = "ALLOWED"
    // 斷言,map 中標識符的值與右邊的字符串相等
    assert map."a b-c" == "ALLOWED"

注:Groovy 中所有的字符串都可以當引用標識符。

2,字符串

Groovy 有 java.lang.Stringgroovy.lang.GString 兩中字符串對象類型。

  • 單引號字符串

    java.lang.String 類型,不支持站位符插值操作。

  • 雙引號字符串

    groovy.lang.GString 類型,支持站位符插值操作。其中插值佔位符我們可以用 ${} 或者 $ 來標示,${} 用於一般替代字串或者表達式,$ 主要用於A.B的形式中。

  • 三重單引號字符串

    java.lang.String 類型,不支持站位符插值操作,可以標示多行字符串。

  • 多重雙引號字符串

    支持站位插值操作,可以標示多行字符串。

  • 斜線字符串

    和雙引號字符串很類似,通常用在正則表達式中。

    def name = 'Test Groovy!'
    def body = 'Test $name'
    // 單引號字符串中,佔位符不會被替換
    assert body == 'Test $name'
    
    // 雙引號字符串,*${}* 標識,括號內面的表達式會被計算,變量會被替換
    def sum = "The sum of 2 and 3 equals ${2 + 3}"
    assert sum.toString() == 'The sum of 2 and 3 equals 5'
    
    // 雙引號字符串,*$* 標識,只對 A.B 有效,對括號、閉包等無效,會拋出 groovy.lang.MissingPropertyException 異常
    def person = [name: 'Guillaume', age: 36]
    assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'
    
    // 三重單引號字符串,不支持站位符插值操作
    def aMultilineString = '''line one
    line two
    line three'''
    
    // 多重雙引號字符串,支持站位符插值操作
    def name = 'Groovy'
    def template = """
        Hello, ${name}
        Welcome.
    """
    
    // 斜線字符串
    def fooPattern = /.*foo.*/
    assert fooPattern == '.*foo.*'
    // 多行支持
    def multilineSlashy = /one
        two
        three/
    // 含站位符使用支持
    def color = 'blue'
    def interpolatedSlashy = /a ${color} car/

3,字符 Characters

Groovy沒有明確的Characters。但是我們可以有如下三種不同的方式來將字符串作爲字符處理,譬如:

char c1 = 'A' 
assert c1 instanceof Character

def c2 = 'B' as char 
assert c2 instanceof Character

def c3 = (char)'C' 
assert c3 instanceof Character

4,數字 Numbers

  • 整型

    和 Java 一樣,支持 byte、char、short、int、long、java.lang.BigInteger

  • 浮點型

    和 Java 一樣,支持 float、double、java.lang.BigDecimal

    // 整型,‘0’開頭,八進制表示
    int xInt = 077
    
    // 整型,‘0x’開頭,十六進制表示
    int xInt = 0x77
    
    // 整型,‘0b’開頭,二進制表示
    int xInt = 0b10101111
    
    // 浮點型,科學計數表示法
    assert 1e3  ==  1_000.0
    assert 2E4  == 20_000.0
    assert 3e+1 ==     30.0
    assert 4E-2 ==      0.04

5,Booleans 類型

def myBooleanVariable = true
boolean untypedBooleanVar = false
booleanField = true

6,Lists 集合

支持 java.util.List, 可以增刪改對象,列表中類型不受限制,可以用超出列表範圍的數來索引列表。

//使用動態List
def numbers = [1, 2, 3]         
assert numbers instanceof List  
assert numbers.size() == 3

//List中存儲任意類型
def heterogeneous = [1, "a", true]

//判斷List默認類型
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList

//使用as強轉類型
def linkedList = [2, 3, 4] as LinkedList    
assert linkedList instanceof java.util.LinkedList

//定義指定類型List
LinkedList otherLinked = [3, 4, 5]          
assert otherLinked instanceof java.util.LinkedList

//定義List使用
def letters = ['a', 'b', 'c', 'd']
//判斷item值
assert letters[0] == 'a'     
assert letters[1] == 'b'
//負數下標則從右向左index
assert letters[-1] == 'd'    
assert letters[-2] == 'c'
//指定item賦值判斷
letters[2] = 'C'             
assert letters[2] == 'C'
//給List追加item
letters << 'e'               
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
//獲取一段List子集
assert letters[1, 3] == ['b', 'd']         
assert letters[2..4] == ['C', 'd', 'e'] 

//多維List支持
def multi = [[0, 1], [2, 3]]     
assert multi[1][0] == 2 

7,Arrays 數組

和 Java 數組類似。

//定義初始化String數組
String[] arrStr = ['Ananas', 'Banana', 'Kiwi']  
assert arrStr instanceof String[]    
assert !(arrStr instanceof List)

//使用def定義初始化int數組
def numArr = [1, 2, 3] as int[]      
assert numArr instanceof int[]       
assert numArr.size() == 3

//聲明定義多維數組指明寬度
def matrix3 = new Integer[3][3]         
assert matrix3.size() == 3

//聲明多維數組不指定寬度
Integer[][] matrix2                     
matrix2 = [[1, 2], [3, 4]]
assert matrix2 instanceof Integer[][]

//數組的元素使用及賦值操作
String[] names = ['Cédric', 'Guillaume', 'Jochen', 'Paul']
assert names[0] == 'Cédric'     
names[2] = 'Blackdrag'          
assert names[2] == 'Blackdrag'

8,Maps 鍵值對

在Groovy中鍵key不一定是String,可以是任何對象(實際上 Groovy 中的 Map 就是java.util.LinkedHashMap)。

//定義一個Map
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']   
//獲取一些指定key的value進行判斷操作
assert colors['red'] == '#FF0000'    
assert colors.green  == '#00FF00'
//給指定key的對賦值value操作與判斷    
colors['pink'] = '#FF00FF'           
colors.yellow  = '#FFFF00'           
assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'
//判斷Map的類型
assert colors instanceof java.util.LinkedHashMap
//訪問Map中不存在的key爲null
assert colors.unknown == null

//定義key類型爲數字的Map
def numbers = [1: 'one', 2: 'two']
assert numbers[1] == 'one'

對於Map需要特別注意一種情況,如下:

//把一個定義的變量作爲Map的key,訪問Map的該key是失敗的
def key = 'name'
def person = [key: 'Guillaume']      
assert !person.containsKey('name')   
assert person.containsKey('key') 

//把一個定義的變量作爲Map的key的正確寫法---添加括弧,訪問Map的該key是成功的
person = [(key): 'Guillaume']        
assert person.containsKey('name')    
assert !person.containsKey('key')   

二、運算符

下面介紹與 Java 不同的運算符,其它請參照 Java。

1,次方運算符(**)

assert  2 ** 3 == 8

def f = 3
f **= 2
assert f == 9

2,非運算符(!)

assert (!true)    == false    
// 支持字符串的判斷,爲空時返回 false,不爲空時返回 true                  
assert (!'foo')   == false                      
assert (!'')      == true 

3,安全佔位符(?.)

這個運算符主要用於避免空指針異常。

def person = Person.find { it.id == 123 }    
def name = person?.name                      
assert name == null  

4,直接域訪問操作符(.@)

因爲Groovy自動支持屬性getter方法,但有時候我們有一個自己寫的特殊getter方法,當不想調用這個特殊的getter方法則可以用直接域訪問操作符。

class User {
    public final String name                 
    User(String name) { this.name = name}
    String getName() { "Name: $name" }       
}
def user = new User('Bob')

assert user.name == 'Name: Bob'
assert user.@name == 'Bob' 

5,方法指針操作符(.&)

因爲閉包可以被作爲一個方法的參數,如果想讓一個方法作爲另一個方法的參數則可以將一個方法當成一個閉包作爲另一個方法的參數。

def list = ['a','b','c']  
//常規寫法 
list.each{  
    println it  
}  

String printName(name){  
    println name  
}  

// 方法指針操作符寫法,將迭代出的每一個值作爲方法的參數
list.each(this.&printName)  

6,三目運算符(?:)

displayName = user.name ? user.name : 'Anonymous'   
// 簡化爲二目運算符,邏輯同上一樣
displayName = user.name ?: 'Anonymous' 

7,展開運算符(*.)

一個集合使用展開運算符可以得到一個元素爲原集合各個元素執行後面指定方法所得值的集合。

cars = [
   new Car(make: 'Peugeot', model: '508'),
   null,                                              
   new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault']     
assert null*.make == null 

三、程序結構

1,包名

和 Java 一致。

// defining a package named com.yoursite
package com.yoursite

2,Imports 引入

常規的導包和 Java 一致,有一個特殊。

//例1:
import groovy.xml.MarkupBuilder

// using the imported class to create an object
def xml = new MarkupBuilder()
assert xml != null

//例2:
import groovy.xml.*

def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null

//例3:
import static Boolean.FALSE

assert !FALSE

//例4:特殊的,相當於用as取別名
import static Calendar.getInstance as now

assert now().class == Calendar.getInstance().class

注意:Groovy與Java類似,已經幫我們默認導入了一些常用的包,所以在我們使用這些包的類時就不用再像上面那樣導入了,如下是自動導入的包列表:

import java.lang.*
import java.util.*
import java.io.*
import java.net.*
import groovy.lang.*
import groovy.util.*
import java.math.BigInteger
import java.math.BigDecimal

3,腳本與類

相對於傳統的Java類,一個包含main方法的Groovy類可以如下書寫:

class Main {                                    
    static void main(String... args) {          
        println 'Groovy world!'                 
    }
}

和Java一樣,程序會從這個類的main方法開始執行,這是Groovy代碼的一種寫法,實際上執行Groovy代碼完全可以不需要類或main方法,所以更簡單的寫法如下:

println 'Groovy world!'

上面這兩中寫法其實是一樣的,具體我們可以通過如下命令進行編譯爲class文件:

groovyc demo.groovy //編譯Groovy源碼爲class

我們使用反編譯工具可以查看到這個demo.groovy類源碼如下:

import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {                     
    def run() {                                 
        println 'Groovy world!'                 
    }
    static void main(String[] args) {           
        InvokerHelper.runScript(Main, args)     
    }
}

可以看見,上面我們寫的groovy文件編譯後的class其實是Java類,該類從Script類派生而來(查閱API);可以發現,每個腳本都會生成一個static main方法,我們執行groovy腳本的實質其實是執行的這個Java類的main方法,腳本源碼裏所有代碼都被放到了run方法中,腳本中定義的方法(該例暫無)都會被定義在Main類中。

通過上面可以發現,Groovy的實質就是Java的class,也就是說他一定會和Java一樣存在變量作用域!對哦,前面我們解釋變量時竟然沒說到這個東東,這裏說下吧。看下面例子:

//單個Groovy源碼文件,運行會報錯找不到num變量
def num = 1 
def printNum(){  
    println num  
}

//單個Groovy源碼文件,運行會報錯找不到num變量
int num = 1 
def printNum(){  
    println num  
}  

//單個Groovy源碼文件,運行OK成功
num = 1 
def printNum(){  
    println num  
} 

上面的例子可以發現,我們如果想要在Groovy的方法中使用Groovy的變量則不能有修飾符。然而,如果我們想在B.groovy文件訪問A.groovy文件的num變量咋辦呢,我們可以使用Field註解,具體操作如下:

import groovy.transform.Field;
@Field num = 1

哈哈,這就是Groovy的變量作用域了,如果你想知道上面這些寫法爲啥出錯,很簡單,自己動手整成Java源碼相信你一定可以看懂爲啥鳥。

四、閉包

1,語法

定義一個閉包:

// [closureparameters -> ]是可選的逗號分隔的參數列表
{ [closureParameters -> ] statements }

參數可以定義,也可以不定義,如果不定義默認有一個 it 的參數。

//使用顯示的名爲參數
{ name -> println name }                            
//接受兩個參數的閉包
{ String x, int y ->                                
    println "hey ${x} the value is ${y}"
}
//包含一個參數多個語句的閉包
{ reader ->                                         
    def line = reader.readLine()
    line.trim()
}

一個閉包其實就是一個groovy.lang.Closure類型的實例,因此可以如下定義:

//定義一個Closure類型的閉包
def listener = { e -> println "Clicked on $e.source" }      
assert listener instanceof Closure
//定義直接指定爲Closure類型的閉包
Closure callback = { println 'Done!' }                      
Closure<Boolean> isTextFile = {
    File it -> it.name.endsWith('.txt')                     
}

調用閉包,可以調用 call,也可以不

def isOdd = { int i-> i%2 == 1 }                            
assert isOdd(3) == true                                     
assert isOdd.call(2) == false

2,參數

參數有如下規則:參數類型可選,參數默認值可選,多個參數必須用逗號隔開。

def closureWithOneArg = { str -> str.toUpperCase() }
assert closureWithOneArg('groovy') == 'GROOVY'

def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'

def closureWithTwoArgs = { a,b -> a+b }
assert closureWithTwoArgs(1,2) == 3

def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
assert closureWithTwoArgsAndExplicitTypes(1,2) == 3

def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
assert closureWithTwoArgsAndOptionalTypes(1,2) == 3

def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }
assert closureWithTwoArgAndDefaultValue(1) == 3

當一個閉包沒有顯式定義一個參數列表時,閉包總是有一個隱式的it參數。

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

當然,如果你想聲明一個不接受任何參數的閉包,且必須限定爲沒有參數的調用,那麼你必須將它聲明爲一個空的參數列表,如下:

def magicNumber = { -> 42 }
// this call will fail because the closure doesn't accept any argument
magicNumber(11)

Groovy的閉包支持最後一個參數爲不定長可變長度的參數,具體用法如下:

def concat1 = { String... args -> args.join('') }           
assert concat1('abc','def') == 'abcdef'                     
def concat2 = { String[] args -> args.join('') }            
assert concat2('abc', 'def') == 'abcdef'

def multiConcat = { int n, String... args ->                
    args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'

3,閉包省略調用

很多方法的最後一個參數都是一個閉包,我們可以在這樣的方法調運時進行略寫括弧。比如:

def debugClosure(int num, String str, Closure closure){  
      //dosomething  
}  

debugClosure(1, "groovy", {  
   println"hello groovy!"  
})

可以看見,當閉包作爲閉包或方法的最後一個參數時我們可以將閉包從參數圓括號中提取出來接在最後,如果閉包是唯一的一個參數,則閉包或方法參數所在的圓括號也可以省略;對於有多個閉包參數的,只要是在參數聲明最後的,均可以按上述方式省略。

四、GDK(Groovy Development Kit)

Groovy除了可以直接使用Java的JDK以外還有自己的一套GDK,其實也就是對JDK的一些類的二次封裝罷了;一樣,這是GDK官方API文檔,寫代碼中請自行查閱。

1,I/O 操作

Groovy提供了很多IO操作的方法,你可以使用Java的那寫IO方法,但是沒有Groovy的GDK提供的簡單牛逼。

//讀文件打印腳本
new File('/home/temp', 'haiku.txt').eachLine { line ->
    println line
}

//讀文件打印及打印行號腳本
new File(baseDir, 'haiku.txt').eachLine { line, nb ->
    println "Line $nb: $line"
}

可以看見,這是一個讀文件打印每行的腳本,eachLine方法是GDK中File的方法,eachLine的參數是一個閉包,這裏採用了簡寫省略括弧。

當然了,有時候你可能更加喜歡用Reader來操作,使用Reader時即使拋出異常也會自動關閉IO。如下:

def count = 0, MAXSIZE = 3
new File(baseDir,"haiku.txt").withReader { reader ->
    while (reader.readLine()) {
        if (++count > MAXSIZE) {
            throw new RuntimeException('Haiku should only have 3 verses')
        }
    }
}

接着我們再看幾個關於讀文件的操作使用,如下:

//把讀到的文件行內容全部存入List列表中
def list = new File(baseDir, 'haiku.txt').collect {it}
//把讀到的文件行內容全部存入String數組列表中
def array = new File(baseDir, 'haiku.txt') as String[]
//把讀到的文件內容全部轉存爲byte數組
byte[] contents = file.bytes

//把讀到的文件轉爲InputStream,切記此方式需要手動關閉流
def is = new File(baseDir,'haiku.txt').newInputStream()
// do something ...
is.close()

//把讀到的文件以InputStream閉包操作,此方式不需要手動關閉流
new File(baseDir,'haiku.txt').withInputStream { stream ->
    // do something ...
}

上面介紹了一些常用的文件讀操作,其它的具體參見API和GDK吧。

寫文件操作:

有了上面的讀操作,接下來直接看幾個寫操作的例子得了,如下:

//向一個文件以utf-8編碼寫三行文字
new File(baseDir,'haiku.txt').withWriter('utf-8') { writer ->
    writer.writeLine 'Into the ancient pond'
    writer.writeLine 'A frog jumps'
    writer.writeLine 'Water’s sound!'
}
//上面的寫法可以直接替換爲此寫法
new File(baseDir,'haiku.txt') << '''Into the ancient pond
A frog jumps
Water’s sound!'''
//直接以byte數組形式寫入文件
file.bytes = [66,22,11]
//類似上面讀操作,可以使用OutputStream進行輸出流操作,記得手動關閉
def os = new File(baseDir,'data.bin').newOutputStream()
// do something ...
os.close()
//類似上面讀操作,可以使用OutputStream閉包進行輸出流操作,不用手動關閉
new File(baseDir,'data.bin').withOutputStream { stream ->
    // do something ...
}

上面介紹了一些常用的文件寫操作,其它的具體參見API和GDK吧。

文件樹操作:

在腳本環境中,遍歷一個文件樹是很常見的需求,Groovy提供了多種方法來滿足這個需求。如下:

//遍歷所有指定路徑下文件名打印
dir.eachFile { file ->                      
    println file.name
}
//遍歷所有指定路徑下符合正則匹配的文件名打印
dir.eachFileMatch(~/.*\.txt/) { file ->     
    println file.name
}
//深度遍歷打印名字
dir.eachFileRecurse { file ->                      
    println file.name
}
//深度遍歷打印名字,只包含文件類型
dir.eachFileRecurse(FileType.FILES) { file ->      
    println file.name
}
//允許設置特殊標記規則的遍歷操作
dir.traverse { file ->
    if (file.directory && file.name=='bin') {
        FileVisitResult.TERMINATE                   
    } else {
        println file.name
        FileVisitResult.CONTINUE                    
    }
}

執行外部程序:

Groovy提供一種簡單方式來處理執行外部命令行後的輸出流操作。如下:

def process = "ls -l".execute()             
println "Found text ${process.text}"

execute方法返回一個java.lang.Process對象,支持in、out、err的信息反饋。在看一個例子,如下:

def process = "ls -l".execute()             
process.in.eachLine { line ->               
    println line                            
}

上面使用閉包操作打印出執行命令行的輸入流信息。

二、有用的工具類操作

ConfigSlurper配置:

ConfigSlurper是一個配置管理文件讀取工具類,類似於Java的*.properties文件,如下:

def config = new ConfigSlurper().parse('''
    app.date = new Date()  
    app.age  = 42
    app {                  
        name = "Test${42}"
    }
''')

assert config.app.date instanceof Date
assert config.app.age == 42
assert config.app.name == 'Test42'

上面介紹了一些常用的屬性配置操作,其它的具體參見API和GDK吧。

Expando擴展:

def expando = new Expando()
expando.toString = { -> 'John' }
expando.say = { String s -> "John says: ${s}" }

assert expando as String == 'John'
assert expando.say('Hi') == 'John says: Hi'

上面介紹了一些常用的拓展操作,其它的具體參見API和GDK吧。

還有很多其他操作,這裏就不一一列舉,詳情參考官方文檔即可,譬如JSON處理、XML解析啥玩意的,自行需求摸索吧。

五,DSL(Domain Specific Languages)領域相關語言

這個就不特殊說明了,只在這裏提一下,因爲我們前邊很多地方已經用過它了,加上我們只是乾貨基礎掌握,所以不做深入探討。

DSL是一種特定領域的語言(功能領域、業務領域),Groovy是通用的編程語言,所以不是DSL,但是Groovy卻對編寫全新的DSL提供了很好的支持,這些支持來自於Groovy自身語法的特性,如下:

  • Groovy不需用定義CLASS類就可以直接執行腳本;
  • Groovy語法省略括弧和語句結尾分號等操作;

所以說這個基礎入門沒必要特別深入理解,簡單的前面都用過了,理解DSL作用即可,點到爲止,詳情參考官方文檔。

參考鏈接:
Groovy腳本基礎全攻略: http://blog.csdn.net/yanbober/article/details/49047515
Gradle腳本基礎全攻略: http://blog.csdn.net/yanbober/article/details/49314255

Gradle 插件解析:

通過自定義 Gradle 插件修改編譯後的 class 文件 https://juejin.im/entry/577b03438ac2470061afb130

在AndroidStudio中自定義Gradle插件 http://blog.csdn.net/huachao1001/article/details/51810328

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