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 類型,由於在這種場景下弱類型就足夠了。
可是,若是你這個類或變量要用於其它模塊的,建議不要使用 def,仍是應該使用 Java 中的那種強類型定義方式,由於使用強類型的定義方式,它不能動態轉換爲其它類型,它可以保證外界傳遞進來的值必定是正確的。若是你這個變量要被外界使用,而你卻使用了 def 類型來定義它,那外界須要傳遞給你什麼纔是正確的呢?這樣會使調用方很疑惑。
若是此時咱們在後面的代碼中改變上圖中 x1 的值爲 String 類型,那麼 x1 又會被編譯器推斷爲 String 類型,以下圖所示:
因而咱們能夠猜想到,其實使用 def 關鍵字定義出來的變量就是 Obejct 類型。
Groovy 中的字符串與 Java 中的字符串有比較大的不一樣,因此這裏咱們須要着重瞭解一下。
Groovy 中的字符串除了繼承了 Java 中傳統 String 的使用方式以前,還 新增 了一個 GString 類型,它的使用方式至少有7、八種,可是經常使用的有三種定義方式。此外,在 GString 中新增了一系列的操做符,這可以讓咱們對 String 類型的變量有 更便捷的操做。最後,在 GString 中還 新增 了一系列好用的 API,咱們也須要着重學習一下。
在 Groovy 中有 三種經常使用 的字符串定義方式,以下所示:
首先,須要說明的是,'無論是單引號、雙引號仍是三引號,它們的類型都是 java.lang.String'。
既生瑜何生亮,其實否則。當咱們編寫的單引號字符串中有轉義字符的時候,須要添加 '',而且,當字符串須要具有多行格式的時候,強行將單引號字符串分紅多行格式會變成由 '+' 號組成的字符串拼接格式。
雙引號不一樣與單、三引號,它定義的是一個可擴展的變量。這裏咱們先看看兩種雙引號的使用方式,以下圖所示:
在上圖中,第一個定義的 author 字符串就是常規的 String 類型的字符串,而下面定義的 study 字符串就是可擴展的字符串,由於它裏面使用了 '${author}' 的方式引用了 author 變量的內容。並且,從其最後的類型輸出能夠看到,可擴展的類型就是 'org.codehaus.groovy.runtime.GStringImpl' 類型的。
須要注意的是,可擴展的字符串是能夠擴展成爲任意的表達式,例如數學運算,以下圖所示:
有了 Groovy 的這種可擴展的字符串,咱們就能夠 避免 Java 中字符串的拼接操做,提高 Java 程序運行時的性能。
這裏,咱們能夠寫一個 小栗子🌰 來看看實際的狀況,以下圖所示:
能夠看到,咱們將 success 字符串傳入了 come 方法,可是最終獲得的類型爲 result,因此,能夠說明 編譯器能夠幫咱們自動在 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 會自動往該索引添加元素。下面,咱們看看 Map 最常使用的幾個操做。
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》
歡迎關注個人微信:
bcce5360
因爲微信羣已超過 200 人,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。
2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~