---
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^ 加入電腦中的環境變量
> 
:::warning
* 如果缺乏 Gradle 可以另外安裝
IntelliJ 開啟 `File` -> `Settings` -> `Plugins` -> `Gradle 安裝`
> 
* 如果自己安裝 Groovy SDK,可以加入 Groovy 環境變量後使用 cmd 確認 (以下以 Window 為例)
> 
:::
```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 ~")
}
}
```
**--實作結果--**
> 
## 基礎語法
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")
```
**--實作結果--**
> 
:::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}"
```
* 當只有一個變量時可以省略大括號 `{}`,當超過一個變數就必須要使用大括號,並使用 `+` 串接
**--實作結果--**
> 
## 數據結構
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
```
> 
* 使用中括弧 `[]` 將元素包裝成 List,預設就為 ArrayList 集合,並且 **如同 Python 可以使用 ^1.^ 負數取出 Array 元素、^2.^ 取出區塊變數`..`**
> 
* 如同 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}"
```
**--實作結果--**
> 
```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 訪問每個元素
**--實作結果--**
> 
* 以下幾種為集合常用的 API,並且 Groovy 也提供了相對好用的一些符號 (語法糖)
1. **添加**
| 符號/函數 | 範例 |
| -------- | -------- |
| << | list << 5 |
| + | list += 5 |
| .add() | list.add(9),也可針對下標增加 list.add(3, 9) |
:::warning
* 這些操作是針對 List 的操作,並不可以操作在 Array 上,否則會報以下錯誤 (也就是找不到 Array 中有這個操作)
> 
:::
```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")
```
**--實作結果--**
> 
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")
```
**--實作結果--**
> 
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")
```
**--實作結果--**
> 
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")
```
**--實作結果--**
> 
### Map 集合
* Map 預設實現類為 `LinkedHashMap`,預設 Key 為 String 類
```groovy=
def myMap = [name: "Alien", age : 18, nackName: "Alien"]
assert myMap instanceof LinkedHashMap
println("myMap = ${myMap.name.class}")
```
> 
* 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}") // 也可使用 '.' 符號
```
**--實作結果--**
> 
:::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()}")
```
> 
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}"
}
```
> 
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")
```
> 
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")
```
> 
<!-- ### 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('-----------------')
```
**--實作結果--**
> 
### 斷言 assert
* Groovy 與 Java 不同,assert 是一值預設為開啟的狀態
```groovy=
println "Assert start"
assert 1 + 1 == 1 // 斷言拋出
println "Assert done"
```
> 
:::warning
* Java、Kotlin 必須對 VM 虛擬機下達 `-ea` 參數,assert 才可以拋出
> 
:::
### 自然語言
* **Groovy 比起 Java 來說更趨近於自然語言**,所謂的**自然語言是相對於人來說的自然**(以人的角度來說使用一個方法應該不需要括號),對於習慣程式撰寫的人來說就不太自然
```groovy=
void say(String message) {
System.out.println(message)
}
// 程式型,非自然
say("Hello")
// 自然語言表示方式
say "Hello"
```
> Groovy 這種表達方式就像是 Kotlin 的 infix 語法
> 
### 運算符重載
* 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()
}
```
> 
## 閉包 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)
}
}
```
**--實作結果--**
> 
:::
* groovy 作為參數傳數方法中使用
:::info
調用 Closure 可以使用 `()`、`call()` 兩種方式
:::
```groovy=
// 由於 groovy 是動態類型語言 - 引數可以省略類型
static def MyEach(closure) {
for(int i in 1..10) {
// 調用傳入的方法
closure("HelloWorld: $i")
}
}
```
**--實作結果--**
> 
### 基礎類閉包
* 以下為 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")
}
```
**--實做結果--**
> 
* 以下是 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}")
```
**--實做結果--**
> 
### 閉包內置參數
* 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()
```
**--實做結果--**
> 
```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()
```
**--實做結果--**
> 
* **==現在定義一個 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()
```
**--實做結果--**
> 
### 閉包委託
* 以下來測試這 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`
:::
**--實作結果--**
> 
* 默認狀況下 `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()
```
**--實作結果--**
> 
:::success
* 這裡設定 `DELEGATE_FIRST` 可以讓 Delegate 的優先提到最前面,這樣才可以真的改變 (當然還有其他的方式可以設定,如下圖)
> 
:::
## 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()
```
**--實作結果--**
> 
### 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;
}
```
**--實作結果--**
> 無法訪問對象種的私有屬性
>
> 
* **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) {
}
}
```
**--實作結果--**
> 
### 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}")
```
**--實作結果--**
> 
### trait
* **trait 介於抽象接口 & 類之間**,它也類似於 Java 的 abstruct,當類要實作時使用 implements (這裡像 Java interface),若是不需要實作的方法則使用 abstruct 描述 (這裡像 Java abstruct)
> 
```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()
```
**--實作結果--**
> 
## 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) // 格式化輸出
```
> 
2. Json 字符串(轉為 Byte 數組) 透過 **JsonSlurper 轉換為集合對象**,`JsonSlurper#parse` 可以轉換多種形態
> 
```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"
```
> 
### 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 操作
> 
* 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
```
> 
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")
```
> 
### 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}")
```
> 
### 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}")
```
> 
## Appendix & FAQ
:::info
:::
###### tags: `Gradle`