Groovy 做爲 Gradle 這一強大構建工具的核心語言,其重要性不言而喻,可是 Groovy 自己是十分複雜的,要想全面地掌握它,我想幾十篇萬字長文也沒法將其完全描述。所幸的是,在 Gradle 領域中涉及的 Groovy 知識都是很是基礎的,所以,本篇文章的目的是爲了在後續深刻探索 Gradle 時作好必定的基礎儲備。html
DSL(domain specific language),即領域特定語言,例如:Matliba、UML、HTML、XML 等等 DSL 語言。能夠這樣理解,Groovy 就是 DSL 的一個分支。java
總的來講,DSL 的 核心思想 就是:「求專不求全,解決特定領域的問題」。node
Groovy 的特色具備以下 三點:android
由於 Groovy 語言相較其它編程語言而言,其 入門的學習成本是很是低的,由於它的語法就是對 Java 的擴展,因此,咱們能夠用學習 Java 的方式去學習 Groovy。git
其特性主要有以下 三種:github
須要注意的是,在咱們使用 Groovy 進行 Gradle 腳本編寫的時候,都是使用的面向過程進行編程的。web
Groovy 的優點有以下 四種:編程
Groovy 官方網址json
從官網下載好 Groovy 文件以後,咱們就能夠看到 Groovy 的目錄結構,其中咱們須要 重點關注 bin 和 doc 這個兩個文件夾。設計模式
bin 文件夾的中咱們須要瞭解下三個重要的可執行命令文件,以下所示:
在 doc 文件夾的下面有一個 html 文件,其中的 api 和 documentation 是咱們須要重點關注的,其做用分別以下所示:
下面是 Groovy 中全部的關鍵字,命名時尤爲須要注意,以下所示:
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 複製代碼
對於每個 field,Groovy 都會⾃動建立其與之對應的 getter 與 setter 方法,從外部能夠直接調用它,而且 在使⽤ object.fieldA 來獲取值或者使用 object.fieldA = value 來賦值的時候,實際上會自動轉而調⽤ object.getFieldA() 和 object.setFieldA(value) 方法。
若是咱們不想調用這個特殊的 getter 方法時則可使用 .@ 直接域訪問操做符。
須要注意的是,咱們在使用的時候,若是當前這個函數是 Groovy API 或者 Gradle API 中比較經常使用的,好比 println,就能夠不帶括號。不然仍是帶括號。否則,Groovy 可能會把屬性和函數調用混淆。
assert (!"android") == false
複製代碼
assert 2 ** 3 == 8
複製代碼
if (android) {}
複製代碼
// 省略了name
def result = name ?: "Unknown"
複製代碼
println order?.customer?.address
複製代碼
注意,swctch 能夠匹配列表當中任一元素,示例代碼以下所示:
// 輸出 ok
def num = 5.21
switch (num) {
case [5.21, 4, "list"]:
return "ok"
break
default:
break
}
複製代碼
Groovy 的基礎語法主要能夠分爲如下 四個部分:
Groovy 中的類型同 Java 同樣,也是分爲以下 兩種:
可是,其實 Groovy 中並無基本類型,Groovy 做爲動態語言, 在它的世界中,全部事物都是對象,就如 Python、Kotlin 同樣:全部的基本類型都是屬於對象類型。爲了驗證這個 Case,咱們能夠新建一個 groovy 文件,建立一個 int 類型的變量並輸出它,會獲得輸出結果爲 'class java.lang.Integer',所以能夠驗證咱們的想法是正確的。實際上,Groovy 的編譯器會將全部的基本類型都包裝成對象類型。
groovy 變量的定義與 Java 中的方式有比較大的差別,對於 groovy 來講,它有 兩種定義方式,以下所示:
若是這個變量就是用於當前類或文件,而不會用於其它類或應用模塊,那麼,建議使用 def 類型,由於在這種場景下弱類型就足夠了。
可是,若是你這個類或變量要用於其它模塊的,建議不要使用 def,仍是應該使用 Java 中的那種強類型定義方式,由於使用強類型的定義方式,它不能動態轉換爲其它類型,它可以保證外界傳遞進來的值必定是正確的。若是你這個變量要被外界使用,而你卻使用了 def 類型來定義它,那外界須要傳遞給你什麼纔是正確的呢?這樣會使調用方很疑惑。
若是此時咱們在後面的代碼中改變上圖中 x1 的值爲 String 類型,那麼 x1 又會被編譯器推斷爲 String 類型,因而咱們能夠猜想到,其實使用 def 關鍵字定義出來的變量就是 Obejct 類型。
Groovy 中的字符串與 Java 中的字符串有比較大的不一樣,因此這裏咱們須要着重瞭解一下。
Groovy 中的字符串除了繼承了 Java 中傳統 String 的使用方式以前,還 新增 了一個 GString 類型,它的使用方式至少有7、八種,可是經常使用的有三種定義方式。此外,在 GString 中新增了一系列的操做符,這可以讓咱們對 String 類型的變量有 更便捷的操做。最後,在 GString 中還 新增 了一系列好用的 API,咱們也須要着重學習一下。
在 Groovy 中有 三種經常使用 的字符串定義方式,以下所示:
首先,須要說明的是,'無論是單引號、雙引號仍是三引號,它們的類型都是 java.lang.String'。
既生瑜何生亮,其實否則。當咱們編寫的單引號字符串中有轉義字符的時候,須要添加 '',而且,當字符串須要具有多行格式的時候,強行將單引號字符串分紅多行格式會變成由 '+' 號組成的字符串拼接格式。
雙引號不一樣與單、三引號,它定義的是一個可擴展的變量。這裏咱們先看看兩種雙引號的使用方式,以下圖所示:
在上圖中,第一個定義的 name 字符串就是常規的 String 類型的字符串,而下面定義的 sayHello 字符串就是可擴展的字符串,由於它裏面使用了 '${name}' 的方式引用了 name 變量的內容。並且,從其最後的類型輸出能夠看到,可擴展的類型就是 'org.codehaus.groovy.runtime.GStringImpl' 類型的。
須要注意的是,可擴展的字符串是能夠擴展成爲任意的表達式,例如數學運算,如上圖中的 sum 變量。
有了 Groovy 的這種可擴展的字符串,咱們就能夠 避免 Java 中字符串的拼接操做,提高 Java 程序運行時的性能。
不須要,編譯器能夠幫咱們自動在 String 和 GString 之間相互轉換,咱們在編寫的時候並不須要太過關注它們的區別。
閉包的本質其實就是一個代碼塊,閉包的核心內容能夠歸結爲以下三點:
clouser.call()
clouser()
def xxx = { paramters -> code }
def xxx = { 純 code }
複製代碼
從 C/C++ 語言的角度看,閉包和函數指針很像,閉包能夠經過 .call 方法來調用,也能夠直接調用其構造函數,代碼以下所示:
閉包對象.call(參數)
閉包對象(參數)
複製代碼
若是閉包沒定義參數的話,則隱含有一個參數,這個參數名字叫 it,和 this 的做用相似。it 表明閉包的參數。表示閉包中沒有參數的示例代碼:
def noParamClosure = { -> true }
複製代碼
函數最後一個參數都是一個閉包,相似於回調函數的用法,代碼以下所示:
task JsonChao {
doLast ({
println "love is peace~"
}
})
// 彷佛好像doLast會當即執行同樣
task JsonChao {
doLast {
println "love is peace~"
}
}
複製代碼
閉包的常見用法有以下 四種:
其差別代碼以下代碼所示:
def scrpitClouser = {
// 表明閉包定義處的類
printlin "scriptClouser this:" + this
// 表明閉包定義處的類或者對象
printlin "scriptClouser this:" + owner
// 表明任意對象,默認與 ownner 一致
printlin "scriptClouser this:" + delegate
}
// 輸出都是 scrpitClouse 對象
scrpitClouser.call()
def nestClouser = {
def innnerClouser = {
// 表明閉包定義處的類
printlin "scriptClouser this:" + this
// 表明閉包定義處的類或者對象
printlin "scriptClouser this:" + owner
// 表明任意對象,默認與 ownner 一直
printlin "scriptClouser this:" + delegate
}
innnerClouser.call()
}
// this 輸出的是 nestClouser 對象,而 owner 與 delegate 輸出的都是 innnerClouser 對象
nestClouser.call()
複製代碼
能夠看到,若是咱們直接在類、方法、變量中定義一個閉包,那麼這三種關鍵變量的值都是同樣的,可是,若是咱們在閉包中又嵌套了一個閉包,那麼,this 與 owner、delegate 的值就再也不同樣了。換言之,this 還會指向咱們閉包定義處的類或者實例自己,而 owner、delegate 則會指向離它最近的那個閉包對象。
其差別代碼以下代碼所示:
def nestClouser = {
def innnerClouser = {
// 表明閉包定義處的類
printlin "scriptClouser this:" + this
// 表明閉包定義處的類或者對象
printlin "scriptClouser this:" + owner
// 表明任意對象,默認與 ownner 一致
printlin "scriptClouser this:" + delegate
}
// 修改默認的 delegate
innnerClouser.delegate = p
innnerClouser.call()
}
nestClouser.call()
複製代碼
能夠看到,delegate 的值是能夠修改的,而且僅僅當咱們修改 delegate 的值時,delegate 的值纔會與 ownner 的值不同。
其示例代碼以下所示:
def stu = new Student()
def tea = new Teacher()
stu.pretty.delegate = tea
// 要想使 pretty 閉包的 delegate 修改生效,必須選擇其委託策略爲 Closure.DELEGATE_ONLY,默認是 Closure.OWNER_FIRST。
stu.pretty.resolveStrategy = Closure.DELEGATE_ONLY
println stu.toString()
複製代碼
須要注意的是,要想使上述 pretty 閉包的 delegate 修改生效,必須選擇其委託策略爲 Closure.DELEGATE_ONLY,默認是 Closure.OWNER_FIRST 的。
Groovy 經常使用的數據結構有以下 四種:
數組的使用和 Java 語言相似,最大的區別可能就是定義方式的擴展,以下代碼所示:
// 數組定義
def array = [1, 2, 3, 4, 5] as int[]
int[] array2 = [1, 2, 3, 4, 5]
複製代碼
下面,咱們看看其它三種數據結構。
即鏈表,其底層對應 Java 中的 List 接口,通常用 ArrayList 做爲真正的實現類,List 變量由[]定義,其元素能夠是任何對象。
鏈表中的元素能夠經過索引存取,並且 不用擔憂索引越界。若是索引超過當前鏈表長度,List 會自動往該索引添加元素。下面,咱們看看 List 最常使用的幾個操做。
def test = [100, "hello", true]
// 左移位表示向List中添加新元素
test << 200
// list 定義
def list = [1, 2, 3, 4, 5]
// 排序
list.sort()
// 使用本身的排序規則
sortList.sort { a, b ->
a == b ?0 :
Math.abs(a) < Math.abs(b) ? 1 : -1
}
複製代碼
// 添加
list.add(6)
list.leftShift(7)
list << 8
複製代碼
// 刪除
list.remove(7)
list.removeAt(7)
list.removeElement(6)
list.removeAll { return it % 2 == 0 }
複製代碼
// 查找
int result = findList.find { return it % 2 == 0 }
def result2 = findList.findAll { return it % 2 != 0 }
def result3 = findList.any { return it % 2 != 0 }
def result4 = findList.every { return it % 2 == 0 }
複製代碼
// 最小值、最大值
list.min()
list.max(return Math.abs(it))
複製代碼
// 統計知足條件的數量
def num = findList.count { return it >= 2 }
複製代碼
表示鍵-值表,其 底層對應 Java 中的 LinkedHashMap。
Map 變量由[:]定義,冒號左邊是 key,右邊是 Value。key 必須是字符串,value 能夠是任何對象。另外,key 能夠用 '' 或 "" 包起來,也能夠不用引號包起來。下面,咱們看看 Map 最常使用的幾個操做。
其示例代碼以下所示:
aMap.keyName
aMap['keyName']
aMap.anotherkey = "i am map"
aMap.anotherkey = [a: 1, b: 2]
複製代碼
若是咱們傳遞的閉包是一個參數,那麼它就把 entry 做爲參數。若是咱們傳遞的閉包是 2 個參數,那麼它就把 key 和 value 做爲參數。
def result = ""
[a:1, b:2].each { key, value ->
result += "$key$value"
}
assert result == "a1b2"
def socre = ""
[a:1, b:2].each { entry ->
result += entry
}
assert result == "a=1b=2"
複製代碼
若是閉包採用兩個參數,則將傳遞 Map.Entry 和項目的索引(從零開始的計數器);不然,若是閉包採用三個參數,則將傳遞鍵,值和索引。
def result = ""
[a:1, b:3].eachWithIndex { key, value, index -> result += "$index($key$value)" }
assert result == "0(a1)1(b3)"
def result = ""
[a:1, b:3].eachWithIndex { entry, index -> result += "$index($entry)" }
assert result == "0(a=1)1(b=3)"
複製代碼
按照閉包的條件進行分組,代碼以下所示:
def group = students.groupBy { def student ->
return student.value.score >= 60 ? '及格' : '不及格'
}
複製代碼
它有兩個參數,findAll 會將 Key 和 Value 分別傳進 去。而且,若是 Closure 返回 true,表示該元素是本身想要的,若是返回 false 則表示該元素不是本身要找的。
表示範圍,它實際上是 List 的一種拓展。其由 begin 值 + 兩個點 + end 值表示。若是不想包含最後一個元素,則 begin 值 + 兩個點 + < + end 表示。咱們能夠經過 aRange.from 與 aRange.to 來獲對應的邊界元素。
若是須要了解更多的數據結構操做方法,咱們能夠直接查 Groovy API 詳細文檔 便可。
若是不聲明 public/private 等訪問權限的話,Groovy 中類及其變量默認都是 public 的。
Groovy 運行時的邏輯處理流程圖以下所示:
爲了更好的講解元編程的用法,咱們先建立一個 Person 類並調用它的 cry 方法,代碼以下所示:
// 第一個 groovy 文件中
def person = new Person(name: 'Qndroid', age: 26)
println person.cry()
// 第二個 groovy 文件中
class Person implements Serializable {
String name
Integer age
def increaseAge(Integer years) {
this.age += years
}
/** * 一個方法找不到時,調用它代替 * @param name * @param args * @return */
def invokeMethod(String name, Object args) {
return "the method is ${name}, the params is ${args}"
}
def methodMissing(String name, Object args) {
return "the method ${name} is missing"
}
}
複製代碼
爲了實現元編程,咱們須要使用 metaClass,具體的使用示例以下所示:
ExpandoMetaClass.enableGlobally()
//爲類動態的添加一個屬性
Person.metaClass.sex = 'male'
def person = new Person(name: 'Qndroid', age: 26)
println person.sex
person.sex = 'female'
println "the new sex is:" + person.sex
//爲類動態的添加方法
Person.metaClass.sexUpperCase = { -> sex.toUpperCase() }
def person2 = new Person(name: 'Qndroid', age: 26)
println person2.sexUpperCase()
//爲類動態的添加靜態方法
Person.metaClass.static.createPerson = {
String name, int age -> new Person(name: name, age: age)
}
def person3 = Person.createPerson('renzhiqiang', 26)
println person3.name + " and " + person3.age
複製代碼
須要注意的是經過類的 metaClass 來添加元素的這種方式每次使用時都須要從新添加,幸運的是,咱們能夠在注入前調用全局生效的處理,代碼以下所示:
ExpandoMetaClass.enableGlobally()
// 在應用程序初始化的時候咱們能夠爲第三方類添加方法
Person.metaClass.static.createPerson = { String name,
int age ->
new Person(name: name, age: age)
}
複製代碼
對於每個 Groovy 腳原本說,它都會生成一個 static void main 函數,main 函數中會調用一個 run 函數,腳本中的全部代碼則包含在 run 函數之中。咱們能夠經過以下的 groovyc 命令用於將編譯獲得的 class 文件拷貝到 classes 文件夾下:
// groovyc 是 groovy 的編譯命令,-d classes 用於將編譯獲得的 class 文件拷貝到 classes 文件夾 下
groovyc -d classes test.groovy
複製代碼
當咱們在 Groovy 腳本中定義一個變量時,因爲它其實是在 run 函數中建立的,因此腳本中的其它方法或其餘腳本是沒法訪問它的。這個時候,咱們須要使用 @Field 將當前變量標記爲成員變量,其示例代碼以下所示:
import groovy.transform.Field;
@Field author = JsonChao
複製代碼
咱們可使用 eachLine 方法讀該文件中的每一行,它惟一的參數是一個 Closure,Closure 的參數是文件每一行的內容。示例代碼以下所示:
def file = new File(文件名)
file.eachLine{ String oneLine ->
println oneLine
}
def text = file.getText()
def text2 = file.readLines()
file.eachLine { oneLine, lineNo ->
println "${lineNo} ${oneLine}"
}
複製代碼
而後,咱們可使用 'targetFile.bytes' 直接獲得文件的內容。
此外,咱們也能夠經過流的方式進行文件操做,以下代碼所示:
//操做 ism,最後記得關掉
def ism = targetFile.newInputStream()
// do sth
ism.close
複製代碼
利用閉包來操做 inputStream,其功能更增強大,推薦使用這種寫法,以下所示:
targetFile.withInputStream{ ism ->
// 操做 ism,不用 close。Groovy 會自動替你 close
}
複製代碼
關於寫文件有兩種經常使用的操做形式,即經過 withOutputStream/withInputStream 或 withReader/withWriter 的寫法。示例代碼以下所示:
def srcFile = new File(源文件名)
def targetFile = new File(目標文件名) targetFile.withOutputStream{ os->
srcFile.withInputStream{ ins->
os << ins //利用 OutputStream 的<<操做符重載,完成從 inputstream 到 OutputStream //的輸出
}
}
複製代碼
def copy(String sourcePath, String destationPath) {
try {
//首先建立目標文件
def desFile = new File(destationPath)
if (!desFile.exists()) {
desFile.createNewFile()
}
//開始copy
new File(sourcePath).withReader { reader ->
def lines = reader.readLines()
desFile.withWriter { writer ->
lines.each { line ->
writer.append(line + "\r\n")
}
}
}
return true
} catch (Exception e) {
e.printStackTrace()
}
return false
}
複製代碼
此外,咱們也能夠經過 withObjectOutputStream/withObjectInputStream 來保存與讀取 Object 對象。示例代碼以下所示:
def saveObject(Object object, String path) {
try {
//首先建立目標文件
def desFile = new File(path)
if (!desFile.exists()) {
desFile.createNewFile()
}
desFile.withObjectOutputStream { out ->
out.writeObject(object)
}
return true
} catch (Exception e) {
}
return false
}
複製代碼
def readObject(String path) {
def obj = null
try {
def file = new File(path)
if (file == null || !file.exists()) return null
//從文件中讀取對象
file.withObjectInputStream { input ->
obj = input.readObject()
}
} catch (Exception e) {
}
return obj
}
複製代碼
首先,咱們定義一個包含 XML 數據的字符串,以下所示:
final String xml = ''' <response version-api="2.0"> <value> <books id="1" classification="android"> <book available="20" id="1"> <title>瘋狂Android講義</title> <author id="1">李剛</author> </book> <book available="14" id="2"> <title>第一行代碼</title> <author id="2">郭林</author> </book> <book available="13" id="3"> <title>Android開發藝術探索</title> <author id="3">任玉剛</author> </book> <book available="5" id="4"> <title>Android源碼設計模式</title> <author id="4">何紅輝</author> </book> </books> <books id="2" classification="web"> <book available="10" id="1"> <title>Vue從入門到精通</title> <author id="4">李剛</author> </book> </books> </value> </response> '''
複製代碼
而後,咱們能夠 使用 XmlSlurper 來解析此 xml 數據,代碼以下所示:
def xmlSluper = new XmlSlurper()
def response = xmlSluper.parseText(xml)
// 經過指定標籤獲取特定的屬性值
println response.value.books[0].book[0].title.text()
println response.value.books[0].book[0].author.text()
println response.value.books[1].book[0].@available
def list = []
response.value.books.each { books ->
//下面開始對書結點進行遍歷
books.book.each { book ->
def author = book.author.text()
if (author.equals('李剛')) {
list.add(book.title.text())
}
}
}
println list.toListString()
複製代碼
獲取 XML 數據有兩種遍歷方式:深度遍歷 XML 數據 與 廣度遍歷 XML 數據,下面咱們看看它們各自的用法,以下所示:
def titles = response.depthFirst().findAll { book ->
return book.author.text() == '李剛' ? true : false
}
println titles.toListString()
複製代碼
def name = response.value.books.children().findAll { node ->
node.name() == 'book' && node.@id == '2'
}.collect { node ->
return node.title.text()
}
複製代碼
在實際使用中,咱們能夠 利用 XmlSlurper 求獲取 AndroidManifest.xml 的版本號(versionName),代碼以下所示:
def androidManifest = new XmlSlurper().parse("AndroidManifest.xml") println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'
複製代碼
除了使用 XmlSlurper 解析 XML 數據以外,咱們也能夠 使用 xmlBuilder 來建立 XML 文件,以下代碼所示:
/** * 生成 xml 格式數據 * <langs type='current' count='3' mainstream='true'> <language flavor='static' version='1.5'>Java</language> <language flavor='dynamic' version='1.6.0'>Groovy</language> <language flavor='dynamic' version='1.9'>JavaScript</language> </langs> */
def sw = new StringWriter()
// 用來生成 xml 數據的核心類
def xmlBuilder = new MarkupBuilder(sw)
// 根結點 langs 建立成功
xmlBuilder.langs(type: 'current', count: '3',
mainstream: 'true') {
//第一個 language 結點
language(flavor: 'static', version: '1.5') {
age('16')
}
language(flavor: 'dynamic', version: '1.6') {
age('10')
}
language(flavor: 'dynamic', version: '1.9', 'JavaScript')
}
// println sw
def langs = new Langs()
xmlBuilder.langs(type: langs.type, count: langs.count,
mainstream: langs.mainstream) {
//遍歷全部的子結點
langs.languages.each { lang ->
language(flavor: lang.flavor,
version: lang.version, lang.value)
}
}
println sw
// 對應 xml 中的 langs 結點
class Langs {
String type = 'current'
int count = 3
boolean mainstream = true
def languages = [
new Language(flavor: 'static',
version: '1.5', value: 'Java'),
new Language(flavor: 'dynamic',
version: '1.3', value: 'Groovy'),
new Language(flavor: 'dynamic',
version: '1.6', value: 'JavaScript')
]
}
//對應xml中的languang結點
class Language {
String flavor
String version
String value
}
複製代碼
咱們能夠 使用 Groovy 中提供的 JsonSlurper 類去替代 Gson 解析網絡響應,這樣咱們在寫插件的時候能夠避免引入 Gson 庫,其示例代碼以下所示:
def reponse =
getNetworkData(
'http://yuexibo.top/yxbApp/course_detail.json')
println reponse.data.head.name
def getNetworkData(String url) {
//發送http請求
def connection = new URL(url).openConnection()
connection.setRequestMethod('GET')
connection.connect()
def response = connection.content.text
//將 json 轉化爲實體對象
def jsonSluper = new JsonSlurper()
return jsonSluper.parseText(response)
}
複製代碼
在這篇文章中,咱們從如下 四個方面 學習了 Groovy 中的必備核心語法:
在後面咱們自定義 Gradle 插件的時候須要使用到這些技巧,所以,掌握好 Groovy 的重要性不言而喻,只有紮實基礎才能讓咱們走的更遠。
二、《慕課網之Gradle3.0自動化項目構建技術精講+實戰》1 - 5章
三、《深刻理解 Android 之 Gradle》