--- title: 'Groovy 語言基礎、閉包' disqus: kyleAlien --- Groovy 語言基礎、閉包 === ## OverView of Content 以語言來說,Java 是靜態語言,而 **Groovy 則是動態語言** [TOC] ## Groovy 概述 Groovy 是基於 JVM 的一種動態語言,可以完全兼容於 Java,並以此為基礎做出拓展,**支持閉包(後面會說名)、DSL**,是靈活的腳本語言; 由於 Gradle 生於 Groovy,要了解 Gradle 就必須要對 Groovy 有基礎的認知。 每個 build 腳本文件都是一個 Groovy 腳本文件,可定義類 class、函數 function、變量屬性等等... :::info Groovy 同時有具備 ^1.^ **物件導向**、^2.^ **腳本** 的特性 ::: ### Groovy 環境 * 我們可以使用 JetBrains 開發的 IntelliJ IDEA,^1^ 下載 IntelliJ 並安裝,^2^ 下載 Groovy SDK 使用,^3^ 加入電腦中的環境變量 > ![](https://i.imgur.com/Fm3JqXx.png) :::warning * 如果缺乏 Gradle 可以另外安裝 IntelliJ 開啟 `File` -> `Settings` -> `Plugins` -> `Gradle 安裝` > ![](https://i.imgur.com/xiyGMrt.png) * 如果自己安裝 Groovy SDK,可以加入 Groovy 環境變量後使用 cmd 確認 (以下以 Window 為例) > ![](https://i.imgur.com/BS8vtQo.png) ::: ```groovy= /** * Java Style */ class HelloGroovy1 { public static void main(String[] args) { System.out.println("Hello Groovy") } } /** * Groovy Style * 省略 public, 引數型態 */ class HelloGroovy2 { static void main(args) { println("Hello Groovy ~") } } ``` **--實作結果--** > ![](https://i.imgur.com/exJgtzT.png) ## 基礎語法 Groovy 不需要分號結尾 `;`,也不需要 return 關鍵字,會自動把最後一行作為返回值,它還有一個高級語言的特色閉包,也就是 Lambda ### Groovy 類型 * Groovy 是強、弱類型混合語言,強類型就是必須宣告變數的型態,像是 C/C++、Java 等語言;弱類型則不需要宣告變數型態 Python、PHP ... * 以下會使用 println + 字符串打印在螢幕上,用法會在字符串時解釋,這邊先關注類型使用 ```groovy= package class_01 /** * Groovy 不需要 Class 開頭 * 1. 可做為腳本語言 * 2. 強、弱類型混合語言 */ char a = 'a' boolean b = true byte c short d int e = 10 long f = 10L float g = 10.2f double h = 10.1 println("Strong type, a class = ${a.class}, a = $a") println("Strong type, b class = ${b.class}, b = $b") // 強類型就算不賦值,也有預設值 def a1 = 20 println("Weak type, a1 class = ${a1.class}, a = $a1") a1 = "20" // 可以自動轉型,跟 Kotlin 的 var 不同 println("Weak type - change type, a1 class = ${a1.class}, a = $a1") //def s2 def s2 = "Hello world" println("Weak type - undefine, a2 class = ${s2.class}, s2 = $s2") ``` **--實作結果--** > ![](https://i.imgur.com/bBHJzXb.png) :::danger groovy 不可以不定義類型,否則會拋出錯誤 ::: ### Groovy 字符串 * 在 Groovy 中分號不是必須的,如同 python (Java、C/C++ 則必須),而表達字串則有兩種方式,單引號 & 雙引號,這兩種方式都可以表達字串,但是仍有差異 | 表達方式 | 特色 | | -------- | -------- | | '單引號' | 單純字符串,如同 Java 的雙引號 | | "雙引號" | 透過特定符號 `${}`,有引用、指標的能力,可以帶到源自串的內容 | | '''三引號''' | 免去換行的功能,主要能夠顯示原來的字串 | 以下編譯 build.gradle 檔案 (預設編譯該檔案) ```groovy= package class_01 def n1 = 'Alien' def n2 = "Alien" // 一般來說換行都必須要跳脫符號,用三引號就不用 def n3 = ''' Hello World Hello Groovy Hello Gradle ''' // 結果都是 String 類 println '單引號獲取 Class: ' + n1.getClass().name + ", n1 =" + n1 println '雙引號獲取 Class: ' + n2.getClass().name + ", n2 =" + n2 println '三引號獲取 Class: ' + n3.getClass().name + ", n3 =" + n3 def name = "Alien" def age = "12" println '單引號${name}' println "雙引號${name}" println "雙引號,單個變數$name" println "雙引號,兩個變數${name+age}" ``` * 當只有一個變量時可以省略大括號 `{}`,當超過一個變數就必須要使用大括號,並使用 `+` 串接 **--實作結果--** > ![](https://i.imgur.com/EywWQ7Q.png) ## 數據結構 Groovy 完全兼容於 Java 集合並進行了拓展,常見的有 List、Set、Map、Queue,並且**集合遍歷方法使用 each 方法** gradle 可以完全使用 JDK 所提供的數據結構 ### List 集合 * Groovy 預設使用 Java 的 ArrayList ```groovy= def myList = [1, 3, 5, 7, 8] assert myList instanceof List assert myList instanceof ArrayList assert myList instanceof LinkedList // throw //assert myList as LinkedList instanceof LinkedList // ok ``` > ![](https://i.imgur.com/klOWa2S.png) * 使用中括弧 `[]` 將元素包裝成 List,預設就為 ArrayList 集合,並且 **如同 Python 可以使用 ^1.^ 負數取出 Array 元素、^2.^ 取出區塊變數`..`** > ![](https://i.imgur.com/QmF8oYg.png) * 如同 Kotlin,可使用 `..` 為集合區段並配合 `in` 使用 ```groovy= def myList = [1, 3, 5, 7, 8] def myList_1 = [1, 3, 5, 7, 8, 11, 13, 16, 18] // as int[] 可轉為 Array println(myList.class.name) println "myList index 0, ${myList[0]}" println "myList index 3, ${myList[3]}" println "myList index -1, ${myList[-1]}" println "myList index -3, ${myList[-3]}" // 可以取出一個區段的 List 集合 `..` println "myList index 0~3, ${myList[0..3]}" println "3 in myList ? ${3 in myList}" println "3 in 0..10 ? ${3 in 0..10}" ``` **--實作結果--** > ![](https://i.imgur.com/QrWnDtU.png) ```groovy= def myList2 = [1, 3, 5, 7, 8] println "---forEach---" def action = { int i -> println "forEach Data: $i" } println "${action.class.name}" myList2.forEach(action) // 閉包 println "---each---" myList2.each { println "each Data: $it" } ``` * 集合 List 中有一個 forEach、each 方法,該方法接收一個 **++閉包作為參數++** (閉包下面會解釋),並使用預設參數 it 訪問每個元素 **--實作結果--** > ![reference link](https://i.imgur.com/TXjnPIp.png) * 以下幾種為集合常用的 API,並且 Groovy 也提供了相對好用的一些符號 (語法糖) 1. **添加** | 符號/函數 | 範例 | | -------- | -------- | | << | list << 5 | | + | list += 5 | | .add() | list.add(9),也可針對下標增加 list.add(3, 9) | :::warning * 這些操作是針對 List 的操作,並不可以操作在 Array 上,否則會報以下錯誤 (也就是找不到 Array 中有這個操作) > ![](https://i.imgur.com/P4BDZhZ.png) ::: ```java= //1. 添加 def myList_1 = [1, 3, 5, 7, 8, 11, 13, 16, 18] // as int[] 可轉為 Array println '-----添加-----' myList_1 << (10) // '>>' 符號會自動添加進原來的集合 println("list '<<' 10 to list: $myList_1") myList_1 += 20 // 括號可不加,也可以寫作為 myList_1 = myList_1 + 20 println("list '+' 20 to list: $myList_1") myList_1.add(30) println("list 'add' 30 to list: $myList_1") ``` **--實作結果--** > ![reference link](https://i.imgur.com/BALA8Mc.png) 2. **刪除** | 符號/函數 | 範例 | | -------- | -------- | | - | list-[2, 4] (刪除集合中的 2, 4 元素) | | remove(index) | list.remove(2) (移除依照 index) | | removeElement | list.remove(2) (移除特定元素) | | removeAll{閉包} | list.removeAll{ it%2 } (閉包後面會說明) | ```java= //2. 刪除 def myList_1 = [1, 3, 5, 7, 8, 11, 13, 16, 18] // as int[] 可轉為 Array println '-----刪除-----' myList_1 -= 30 // 沒有 30 這個元素,所以不會有改動 println("list '-' 30 in list: $myList_1") myList_1 -= [1, 3] println("list '- []' 1, 3 in list: $myList_1") myList_1.remove((Object)5) // 強轉移除特定元素 println("list 'remove' in list: $myList_1") myList_1.removeElement(7) println("list 'removeElement' in list: $myList_1") myList_1.removeAll {it%2 != 0 } // 移除所有餘數 != 0 的元素 println("list 'removeAll' in list: $myList_1") ``` **--實作結果--** > ![](https://i.imgur.com/2IYxM2z.png) 3. **查找 / 遍歷** | 符號/函數 | 範例 | | -------- | -------- | | find{閉包} | list.find{ it%2 } | | findAll{閉包} | list.findAll{ it%2 } | | any{} | list.any{ it%2 } | | every{} | list.every{ it%2 } | | max | list.max{ Math.abs(it) } 找最大值 | | min | list.min{ Math.abs(it) } 找最小值 | | count | list.count{it > 0} 統計 | ```groovy= //3. 查找 myList_1 = [-20, 1, 3, 5, 7, 8, 11, 13, 16, 18] println '-----查找-----' def r = myList_1.find {it % 2 == 0 } // 找到第一個符合條件的元素 println("list 'find closure' in list: $r") r = myList_1.findAll { it % 2 == 0 } // 找所有符合條件的元素並返回新集合 println("list 'findAll closure' in list: $r") r = myList_1.any {it % 2 == 0 } // 判斷集合中是否有一個元素 println("list 'any closure' in list: $r") r = myList_1.every { it % 2 == 0 } // 判斷集合中是否全部都符合該條件 println("list 'every closure' in list: $r") r = myList_1.max {Math.abs(it) } // 找出集合中最大的元素 println("list 'max closure' in list: $r") r = myList_1.min {Math.abs(it) } // 找出集合中最大的元素 println("list 'min closure' in list: $r") r = myList_1.count { it > 0 && it % 2 != 0 } // 依照條件統計數量 println("list 'count closure' in list: $r") ``` **--實作結果--** > ![](https://i.imgur.com/AVuWrgL.png) 4. **排序** | 符號/函數 | 範例 | | -------- | -------- | | sort{閉包} | list.sort{條件} 小到大排序 (升序) | ```groovy= // 排序 myList_1 = [-20, 1, 3, 5, 7, 8, 11, 13, 16, 18] println '-----排序-----' def sort = myList_1.sort {Math.abs(it) } println("list 'sort' with integer Element: $sort") myList_1 = ["YB", "YoBoy", "YoYoBoy", "YoYoYoBoy"] sort = myList_1.sort {it.length() } println("list 'sort' with String Element: $sort") ``` **--實作結果--** > ![](https://i.imgur.com/2NZzlNZ.png) ### Map 集合 * Map 預設實現類為 `LinkedHashMap`,預設 Key 為 String 類 ```groovy= def myMap = [name: "Alien", age : 18, nackName: "Alien"] assert myMap instanceof LinkedHashMap println("myMap = ${myMap.name.class}") ``` > ![](https://i.imgur.com/vJN6Gns.png) * Map 是 `key` : `value` 相互對應,給予 Key 就可以取出對應的 value ```groovy= def myMap = [name: "Alien", age : 18, nackName: "Alien"] // 預設類 默認使用 LinkedHashMap 可以使用 as ... 改變 println("myMap.getClass: ${myMap.getClass()}") // 如果使用 .class,就會把 class 作為 key println("myMap[0]: ${myMap[0]}") // 無法透過下標值獲取 println("myMap[name]: ${myMap['name']}") // 必須透過 Key 獲取 println("myList.age: ${myMap.age}") // 也可使用 '.' 符號 ``` **--實作結果--** > ![](https://i.imgur.com/Lz9xB49.png) :::warning * 這邊可以看到,以往同一個集合 Key、Value 兩者的型態是固定的 (Java 使用泛型後就固定了),但是這邊可以看的出來 **Gradle 在同一個集合可以有不同值的相對應** * 如果 Key 要使用變數引入,就必須添加 `()` 符號,否則它會辨認為 String 參數 ```groovy= def key = "Phone" // def myMap2 = [key: 123456789] // Error def myMap2 = [(key): 123456789] myMap2.each { println it.key } assert myMap2.containsKey("Phone") ``` ::: 1. **添加數據** ```groovy= // ------------ 添加元素 def myMap = [name: "Alien", age : 18, nackName: "Alien"] myMap.id = 9527 println("myMap = $myMap") // Map 內添加 Map myMap.mine = [key : "Hello", value : "World"] println("myMap = ${myMap.toMapString()}") ``` > ![](https://i.imgur.com/ou09iEc.png) 2. **遍歷數據 (使用閉包)** ```groovy= // ------------ 遍歷 def myMap = [name: "Alien", age : 18, nackName: "Alien"] // each 閉包 -1 myMap.each { println "each_1 -> it.key: ${it.key}, it.value: ${it.value}" } // each 閉包 -2 myMap.each { def name, def info -> println "each_2 -> name: ${name}, info: ${info}" } // 帶 index myMap.eachWithIndex{ def entry, int i -> println "each_3 -> index: ${i}, key: ${entry.key}, value: ${entry.value}" } // 帶 index myMap.eachWithIndex{ def key, def value, int i -> println "each_4 -> index: ${i}, key: ${key}, value: ${value}" } ``` > ![](https://i.imgur.com/Y7FXZNU.png) 3. **查找 (常用 API)** ```groovy= //------------ 查找 API def myMap = [name: "Alien", age : 18, nackName: "Alien"] def result = myMap.find { // 找第一個符合的數據 return it.key == "name" } println("find in Map: $result") result = myMap.findAll { return it.value == "Alien" } println("findAll in Map: $result") result = myMap.count { return it.value == "Alien" } println("count in Map: $result") ``` > ![](https://i.imgur.com/JyawJA7.png) 4. **排序**:這裡與 List 差不多 (同樣是升序),但是 **==Map 會返回新的對象,List 則會改變來源對象==** ```groovy= // Sort: Map 會返回新的對象,List 則會改變來源 對象 def myMap = [name: "Alien", age : 18, nackName: "Alien"] result = myMap.sort { return it.key.length() } println("sort in Map: $result") ``` > ![](https://i.imgur.com/BTiPLxd.png) <!-- ### Range --> ## Groovy 方法 * Groovy 是比較新、高階的語言,主要介紹與 Java 方法使用的不同;其中包括 **可省略括號 `()`、可省略 return** 符號... 等等 ```groovy= task callMethod { method_1(1, 2) method_1 1, 6 //method_1 -1, 5 負號則一定要使用 `()` def result = method_2 (1, 3) println "method_2 = ${result}" } def method_1(int a, int b) { println "a+b = ${a+b}" } def method_2(int a, int b) { // 未寫 return 則自動挑選最後一行程是作為返回值 a > b ? a : b } ``` :::danger 呼叫函數時,**如有負號,則不能省略 `()` 否則編譯會錯誤**,因為無法正常解析語法 ::: ### 傳遞方法 - Lambda * Lambda 表達式 **可以把函式作為參數傳入方法內部**,其以兩個特性 ^1.^ 匿名內聯(可以想像成是 Java 的匿名內部類)、^2.^ 連接兩個不同的函數 :::success Groovy 的省略方式如同 [**Kotlin**](https://hackmd.io/YYd13a9nS7qKTDfuLS6pNA?view#Lambda-amp-Kotlin) 一般 ::: ```groovy= // 推敲 each 方法 def myList = [1, 3, 5, 6, 7] //0. 閉包原型 Closure closure = {int i -> println "Step 0 = $i"} // Each 原型: // public static <T> List<T> each(List<T> self, @ClosureParams(FirstGenericType.class) Closure closure) {} myList.each (closure) println('-----------------') //1. 若只有一個參數就直接使用 it 即可 myList.each( {println "Step 1 = $it"} ) println('-----------------') //2. Groovy 規定,如果方法的最後一個參數是閉包,可以放置方法外面 myList.each() { println "Step 2 = $it" } println('-----------------') // 3. 如同上面所說,呼叫方法可以省略 () myList.each { println "Step 3 = $it" } println('-----------------') ``` **--實作結果--** > ![](https://i.imgur.com/vBCTHLi.png) ### 斷言 assert * Groovy 與 Java 不同,assert 是一值預設為開啟的狀態 ```groovy= println "Assert start" assert 1 + 1 == 1 // 斷言拋出 println "Assert done" ``` > ![](https://i.imgur.com/HH1Nxdp.png) :::warning * Java、Kotlin 必須對 VM 虛擬機下達 `-ea` 參數,assert 才可以拋出 > ![](https://i.imgur.com/gJGspx5.png) ::: ### 自然語言 * **Groovy 比起 Java 來說更趨近於自然語言**,所謂的**自然語言是相對於人來說的自然**(以人的角度來說使用一個方法應該不需要括號),對於習慣程式撰寫的人來說就不太自然 ```groovy= void say(String message) { System.out.println(message) } // 程式型,非自然 say("Hello") // 自然語言表示方式 say "Hello" ``` > Groovy 這種表達方式就像是 Kotlin 的 infix 語法 > ![](https://hackmd.io/_uploads/By5RAvV33.png) ### 運算符重載 * Groovy 比起 Java 來說更加自由的點在於它可以做運算符重載,透過 Override 操作對應的函數;下面我們來重載 `+` 號(對應的就是 `plus` 函數) ```groovy= class Operator { private int value // 重載 Operator plus(Operator operator) { return new Operator(value: this.value + operator.value) } void show() { System.out.println("value: " + value) } } static void main(String[] args) { def a = new Operator(value: 10) (a + new Operator(value: 20)).show() } ``` > ![](https://hackmd.io/_uploads/ByX3Nd432.png) ## 閉包 Closure * 閉包是 Groovy 很重要的特性 (但並非 Groovy 獨有),是 DSL 的基礎。其表示方是 **可以想做一個 Java 的匿名內部類 (或是 Lambda),是可以重複覆用的**,但是更加的簡潔 :::info 閉包如同 Lambda 表達,也可以想做把一個方法包裝後,作為參數傳入,在該方法內在調用 ::: * groovy 閉包使用 ```groovy= // 正常使用 MyEach( {println it} ) // 當最後一個參數為閉包時可以放置方法外面 MyEach() { println it } // 只有一個 參數可以使用 it,一個以上就不行 !!! MyEach { println it } ``` :::success * **當閉包 ++只有一個參數時,該參數默認為 `it`++,當超出一個參數時就不能使用 `it`,必須將引數分開細寫** (同 Kotlin 一樣) ```groovy= // 依照上面推理 eachMap 其實就是方法 eachMap { // 兩個參數不可以使用 it k, v -> // Lambda 需要大括號,groovy 不需要 println "name: $k, age: $v" println "123-----------" } static def eachMap(closure) { def mapp1 = ["name":'Alien', "age":18] mapp1.each { closure(it.key, it.value) } } ``` **--實作結果--** > ![](https://i.imgur.com/vxLp8Ng.png) ::: * groovy 作為參數傳數方法中使用 :::info 調用 Closure 可以使用 `()`、`call()` 兩種方式 ::: ```groovy= // 由於 groovy 是動態類型語言 - 引數可以省略類型 static def MyEach(closure) { for(int i in 1..10) { // 調用傳入的方法 closure("HelloWorld: $i") } } ``` **--實作結果--** > ![reference link](https://i.imgur.com/Kta9hZp.png) ### 基礎類閉包 * 以下為 int 基礎類的閉包方法,每次遍歷都會呼叫一次閉包(使用 Closure#call 方法) | int 方法 | 功能 | | -------- | -------- | | upto (閉包) | 正序表達 | | downto (閉包) | 逆序表達 | | times (閉包) | 正序表達,左開右閉(不包括最後一個數) | ```groovy= /** * upto 原型 * * public static void upto(Number self, Number to, @ClosureParams(FirstParam.class) Closure closure) { * int self1 = self.intValue(); * int to1 = to.intValue(); * // 確定順序大小沒有錯誤 * if (self1 > to1) { * throw new GroovyRuntimeException("The argument (" + to + ") to upto() cannot be less than the value (" + self + ") it's called on."); * } else { * for(int i = self1; i <= 1; ++i) { * / 呼叫一次閉包 * closure.call(i); * } * } * } */ int a = 1 def result = 1 a.upto(5, { i -> result = result * i println("upto, index = $i, result = $result") }) // 反知 downto 序 int b = 5 result = 1 b.downto(1) { i -> result = result * i println("downto, index = $i, result = $result") } int c = 5 result = 5 c.times {i -> println("times, index = $i") } ``` **--實做結果--** > ![](https://i.imgur.com/aUmhl5K.png) * 以下是 String 類常使用的 api,**當使用到 Closure 都必須看源碼,看它如何是用的** | String 方法 | | | -------- | -------- | | multiply | 重複規定次數的每個字符 | | find | 找字符串,有找到則直接返回該字符 | | findAll | 找到指定字符串,並返回指定字符的集合 | | any | 該字符串內只要有一個字符符合就可以成立 | | every | 判斷是否每一個元素都為指定值 | | collect | 對每個字符操作,完成後返回一個新的集合 | ```groovy= /** * multiply */ String str1 = "123 456 789 Hello Lambda" str1 = str1.multiply(2) // Sample : str *= 2 (重複 2 次字串) println("muiply: $str1") str1 = "123 456 789 Hello Lambda" /** * find */ def result = str1.find() { it == 'p' // cannot find null } println("find: $result") /** * findAll */ result = str1.findAll() { it == 'a' } println("finAll: $result, type: ${result.class.name}") /** * any */ result = str1.any() { it == 'a' } println("any: $result, type: ${result.class.name}") /** * every */ result = str1.every { it == 'a' } println("every: $result, type: ${result.class.name}") /** * collect */ result = str1.collect { it + "-" } println("collect: $result, \ntype: ${result.class.name}") ``` **--實做結果--** > ![](https://i.imgur.com/g5yF308.png) ### 閉包內置參數 * Groovy 的強大之處在於它支持 **閉包的委託**,而 **==Groovy 的閉包有以下三種屬性==** (閉包自帶以下屬性) | 閉包隱式屬性 | 說明 | | -------- | -------- | | thisObject / this | 建構腳本的上下文,可看為 Java 的 this 關鍵字,也就是 **當前的類** | | owner | 不可修改,**閉包定義處的類(最外部的類)** | | delegate |可修改,可為任意對象,預設與 owner 指向的對象相同 | 以下使用 Class 類內部的方法測試 **不同 Closure 內部鎖指向的對象是否相同**,運形程式後,從結果可以看到是相同的 ```groovy= def closure = { println("this: $this") println("thisObject: $thisObject") println("owner: $owner") println("delegate: $delegate") } closure.call() ``` **--實做結果--** > ![](https://i.imgur.com/ceWjQUf.png) ```groovy= class Test { def closure = { println("------------ normal closure") println("this: $this") println("thisObject: $thisObject") println("owner: $owner") println("delegate: $delegate") } def static method() { def inner = { println("------------ method inner closure") println("this: $this") println("thisObject: $thisObject") println("owner: $owner") println("delegate: $delegate") } inner.call() } } def t = new Test() t.closure.call() Test.method() ``` **--實做結果--** > ![](https://i.imgur.com/8A71syx.png) * **==現在定義一個 Nest Closure==** ```kotlin= def outside = { def inner = { println("------------ inner closure") // 指向自身的 closure println("this: " + this) println("thisObject: " + thisObject) // 指向外部的 closure println("owner: " + owner) println("delegate: " + delegate) // println("delegate: $delegate") Error: java.lang.StackOverflowError } inner.call() } outside.call() ``` **--實做結果--** > ![](https://i.imgur.com/hzcsSbE.png) ### 閉包委託 * 以下來測試這 3 個預設參數,還有對應 `it` 的功能 ```groovy= // Inner_params_4.groovy TestMethod t1 = new TestMethod() // 必須 new 關鍵字 (Kotlin、Dart 不需要) t1.test { println '-----------------init params---------------------' println "thisObject -> ${thisObject.getClass().name}" println "owner -> ${owner.getClass().name}" println "delegate -> ${delegate.getClass().name}" println '-----------------Call file Method---------------------' // 測試 1 printMsg() println '-----------------Call Method by object---------------------' // 測試 2 t1.printMsg() // 測試 3 t1.test() { // it 代表 TestMethod it.printMsg() } } void printMsg() { // 這裡的 this 是檔案自身 println "Context this:${this.getClass()} in Root" } class TestMethod { void printMsg() { // 這裡的 TestMethod 是檔案自身 println "TestMethod this:${this.getClass()} in Class" } void test(closure) { // 單個參數 // 傳入自身 this closure(this) } } ``` :::info 閉包內方法的處理順序:`thisObject` > `owner` > `delegate` ::: **--實作結果--** > ![](https://i.imgur.com/yb4nfVs.png) * 默認狀況下 `owner == delegate` **主要差異是在於 ==delegate 可以修改==**,以下來嘗試修改 delegate,**讓其不需要透過 it 來呼叫對象方法** > 下面的概念就是修改 閉包內的 delegate ```groovy= class Student { String name // define closure def say = { println "My name is $name" println "thisObject -> ${thisObject.getClass().name}" println "owner -> ${owner.getClass().name}" println "delegate -> ${delegate.getClass().name}\n" } String toString() { // 呼叫自己的閉包 say.call() } } class Teacher { String name } def student = new Student(name: "Pan") def teacher = new Teacher(name: "Teacher Alien") println "original:" student.toString() // ----------------------------------------------------------------- // 修改 delegate student.say.delegate = teacher println "change student delegate:" // 相同 student.toString() // ----------------------------------------------------------------- // 改變閉包的順序 // 設定閉包委託策略 student.say.resolveStrategy = Closure.DELEGATE_FIRST // 改變 println "change student delegate to DELEGATE_FIRST:" student.toString() student.say.resolveStrategy = Closure.OWNER_FIRST // this 優先 println "change student delegate to OWNER_FIRST:" student.toString() ``` **--實作結果--** > ![](https://i.imgur.com/A4wZvYG.png) :::success * 這裡設定 `DELEGATE_FIRST` 可以讓 Delegate 的優先提到最前面,這樣才可以真的改變 (當然還有其他的方式可以設定,如下圖) > ![](https://i.imgur.com/joKEHhS.png) ::: ## Groovy 物件導向 Groovy 中主要有分為 class、interface(前兩個主要看與 Java 的差異)、trait 這三個種類,**trait 相當於定義 interface 的適配器** 1. 在 Groovy 中所有的默認值都是 **public** 2. 所有的類都預定繼承於 **GroovyObject**(可以當成是 Java 中是 Object) ### class 構造方法 * **groovy 中可以在建立新對象時指定對象中的屬性並賦予值 (==包括私有屬性==)**,並且類的建立必須使用關鍵字 new (Dart、Kotlin 則不需要 new 關鍵字) ```groovy= class GroovyMackClass { String name int age private long id void show() { println "name: $name, age: $age, id: $id" } } // 可指定值,而 object construct 可以不用設定引數 def g1 = new GroovyMackClass(name: "Alien", age: 12) g1.show() // 可不用按照順序 def g2 = new GroovyMackClass(age: 13, name: "kyle") g2.show() // 私有屬性也可以指定 def g3 = new GroovyMackClass(name: "Pan", age: 15, id: 9527) g3.show() ``` **--實作結果--** > ![](https://i.imgur.com/rdqBlG7.png) ### POJO / Bean * 也可以應該稱之為 JavaBean (有帶邏輯,POJO 則是單純的 setter/getter 方案),以往為了寫 setter/getter 方法會拉長整體程式,但是 **Groovy 的 setter/getter 是自動封裝** ```groovy= task POJO { // 新增 Info 對象 Info info = new Info(); // 未設定 println "name: $info.name, age: $info.age, id: $info.id" // 設定 info.name = 'Alien' info.age = 13 info.id = 7654321 println "name: $info.name, age: $info.age, id: $info.id" } class Info { // 設為私有,也就是不能取,並且沒有設置 setter/getter 方法 private String name; private int age; private long id; } ``` **--實作結果--** > 無法訪問對象種的私有屬性 > > ![](https://i.imgur.com/gmUbmQz.png) * **Groovy 可不用設定屬性而透過方法取得**,並且可以鎖定不讓其全部取得 (並且不須設定屬性),並依照以下規則 1. 該函數的命名方式則需要固定,**get+屬性名、set+屬性名** 2. **不可為私有函數**,否則無法搜尋到 3. 必須要**使用括號** ```groovy= package class_02 Data d = new Data() println "length 屬性 = $d.length" d.head = 20; // 有 setter 方法 println "head = ${d.head}" //這無法找到 head 屬性,因為目前只有設定 setter class Data { // 不可為私有 private,也可取名為 getlength public int getLength() { // 不需要屬性也可以設置 3 } public void setHead(int head) { } } ``` **--實作結果--** > ![](https://i.imgur.com/5scQjx6.png) ### interface 接口 * 接口中是不能定義非 public 的方法 (如同 Java),而且 Groovy 不需要 public 所以可以省略 ```groovy= // 省略 public interface Info { // 接口中只能定義 public funcion void setName(String name); String getName(); } class Student implements Info { private String mName @Override void setName(String name) { mName = name } @Override String getName() { return mName } } def s1 = new Student() s1.name = "Android_Gradle" println("interface: ${s1.name}") ``` **--實作結果--** > ![](https://i.imgur.com/CON5Rrs.png) ### trait * **trait 介於抽象接口 & 類之間**,它也類似於 Java 的 abstruct,當類要實作時使用 implements (這裡像 Java interface),若是不需要實作的方法則使用 abstruct 描述 (這裡像 Java abstruct) > ![](https://i.imgur.com/u2FAFxa.png) ```groovy= trait MyTrait { abstract void Hello() void World() { println("Hello Groovy Trait") } } class TestTrait implements MyTrait { @Override void Hello() { println("To Do Trait") } } def r = new TestTrait() r.Hello() r.World() ``` **--實作結果--** > ![](https://i.imgur.com/eW18X6e.png) ## Json / XML 解析 基礎原本是 [**Java 的解析方式**](https://hackmd.io/QXvnZVGjS_-oRZTbPO3YWg?view#XML-%E6%A0%BC%E5%BC%8F) Groovy 都可以使用,以下說說與 Java 不同的地方 ### Json 解析轉換 1. 集合可以透過 **JsonOutput** 自動轉換為 Json 檔案格式 ```groovy= // 引入 JsonOutput import groovy.json.JsonOutput class PersonInfo { String name int age } def list = [ new PersonInfo(name: "Jason", age: 20), new PersonInfo(name: "kyle", age: 15) ] String convert = JsonOutput.toJson(list) println convert println JsonOutput.prettyPrint(convert) // 格式化輸出 ``` > ![](https://i.imgur.com/kiQRfJc.png) 2. Json 字符串(轉為 Byte 數組) 透過 **JsonSlurper 轉換為集合對象**,`JsonSlurper#parse` 可以轉換多種形態 > ![reference link](https://i.imgur.com/CKFPUS9.png) ```groovy= // 轉換為 Array 數組 def jsonSlurper = new JsonSlurper() def result = jsonSlurper.parse( "[{\"age\":20,\"name\":\"Jason\"},{\"age\":15,\"name\":\"kyle\"}]".bytes // 字串轉為 bytes ) println "after parse class: ${result.class}\nresult: $result" ``` > ![](https://i.imgur.com/8eVsVP9.png) ### Xml 解析轉換 * 以下取用 Android Manifest 來代表 (Manifest 就是 Xml 格式),並使用 **Slurper** 1. 解析 Xml ```groovy= import groovy.xml.XmlSlurper def android_manifest = ''' <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.useActiviy"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".SecondActivity"> </activity> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> ''' // xml 解析 def xmlSlurper = new XmlSlurper() def result = xmlSlurper.parseText(android_manifest) println result.@package // 訪問 package 屬性 println result.application.text() // Activity 的內容 result.application.activity.each { // Application 下的所有 Activity println "application.activity: ${it.@'android:name'}" } // 讀取域名空間 def rd = result.declareNamespace(android : '"http://schemas.android.com/apk/res/android') // 讀取指定根結點 println result.application.@'android:allowBackup' // 給予 key 就可以找到 value //println result.application.@'android:icon' //println result.application.activity[0].@'android:name' //println result.application.activity[1].@'android:name' ``` <!-- 2. 創建 // TODO: --> ## 其他 ### IO 操作 > ![](https://i.imgur.com/oORHKoH.png) * Groovy 讀取 IO 更加簡潔,不須 try/catch 和 IO 開關 1. 讀 File ```java= def filePath = "C:\\Users\\User\\IdeaProjects\\Groovy\\src\\io\\MyTextRead.txt" def file = new File(filePath) file.eachLine { println("File content: $it") } println file.text ``` > ![](https://i.imgur.com/HWpV1gW.png) 2. 寫 File ```java= def filePath = "C:\\Users\\User\\IdeaProjects\\Groovy\\src\\io\\MyTextWrite.txt" def file = new File(filePath) file.withPrintWriter { it.write("Write ~~ Groovy with write\n") it.println("Write with println") } file.append("Append~ :D") ``` > ![](https://i.imgur.com/NvrTRQs.png) ### NPE 判斷 * 可以使用 `.?` 符號判斷該物件是否為 NULL,不為 NULL 才往下執行 ```java= class Text { def length def content Text(String content) { this.content = content this.length = content.length() } } def t = new Text("Hello") println("Content: ${t.content}, len: ${t.length}") t = null println("Content: ${t?.content}, len: ${t?.length}") ``` > ![](https://i.imgur.com/odfupLG.png) ### with 操作符 * with 是一個語法糖,可以對一個類快速賦予值 ```groovy= class Text { def length def content } def t = new Text() t.with { def str = "Hello" it.content = str it.length = str.length() } println("Content: ${t.content}, len: ${t.length}") ``` > ![](https://i.imgur.com/YGTrxIg.png) ## Appendix & FAQ :::info ::: ###### tags: `Gradle`