一、環(huán)境搭建
- 安裝好JDK環(huán)境
- 到groovy官網(wǎng)下載groovySDK,解壓到合適位置
groovy官網(wǎng):http://www.groovy-lang.org/
image.png
安裝后的文件如上圖所示,我們需要關(guān)注的是bin和doc文件夾下的內(nèi)容,其它文件夾下是一些配置和groovy自帶的一些jar包 - 配置groovy環(huán)境變量
二、與Java的不同之處
1、默認 imports
所有這些包和類都是默認導入的,您不必使用顯式import語句來使用它們:
java.io.*
java.lang.*
java.math.BigDecimal
java.math.BigInteger
java.net.*
java.util.*
groovy.lang.*
groovy.util.*
2、運行時分派
在Groovy中,將在運行時選擇將被調(diào)用的方法。 這稱為運行時分派或Multi-methods。 這意味著將基于運行時參數(shù)的類型來選擇方法。 在Java中,則是根據(jù)聲明的類型,在編譯時選擇方法。
下面的代碼,以Java代碼編寫,可以在Java和Groovy中編譯,但它的行為會有所不同:
int method(String arg) {
return 1;
}
int method(Object arg) {
return 2;
}
Object o = "Object";
int result = method(o);
在Java中, 您將得到:2
而在Groovy中:1
這是因為Java將使用靜態(tài)信息類型,即o被聲明為Object,而Groovy將在運行時選擇該方法被實際調(diào)用時。 因為它是用String調(diào)用的,所以調(diào)用String版本。
3、數(shù)組初始化
在Groovy中,{...}塊是為閉包而保留的。 這意味著您不能使用以下語法創(chuàng)建數(shù)組:
int [] array = {1,2,3}
你必須使用:
int [] array = [1,2,3]
4、自動資源管理塊
roovy不支持Java 7中的ARM(自動資源管理)塊。 相反,Groovy提供了依賴閉包的各種方法,它們具有相同的效果,同時更加方便。 例如:
Path file = Paths.get("/path/to/file");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
可以這樣寫:
new File('/path/to/file').eachLine('UTF-8') {
println it
}
或者,如果你想要一個更接近Java的版本:
new File('/path/to/file').withReader('UTF-8') { reader ->
reader.eachLine {
println it
}
}
5、Lambdas
Java 8支持lambdas和方法引用:
Runnable run = () -> System.out.println("Run");
list.forEach(System.out::println);
Java 8 lambdas可以或多或少被認為是匿名內(nèi)部類。 Groovy不支持該語法,但是可以使用閉包:
Runnable run = { println 'run' }
list.each { println it } // or list.each(this.&println)
6、GStrings
由于雙引號字符串字面量被解釋為GString,Groovy將在GString和String之間自動轉(zhuǎn)換
Groovy中的單引號用于String,雙引號結(jié)果是String或GString,取決于文字中是否有插值。
assert 'c'.getClass()==String
assert "c".getClass()==String
assert "c${1}".getClass() in GString
只有在賦給char類型的變量時,Groovy會自動將單字符String轉(zhuǎn)換為char。 當調(diào)用類型為char的參數(shù)的方法時,我們需要顯式轉(zhuǎn)換或確保該值已預先轉(zhuǎn)換。
char a='a'
assert Character.digit(a, 16)==10 : 'But Groovy does boxing'
assert Character.digit((char) 'a', 16)==10
try {
assert Character.digit('a', 16)==10
assert false: 'Need explicit cast'
} catch(MissingMethodException e) {
}
Groovy支持兩種類型的轉(zhuǎn)換,在轉(zhuǎn)換為char的情況下,在轉(zhuǎn)換multi-char 時存在微妙的差別。 Groovy風格的轉(zhuǎn)換是更寬松的,將采取第一個字符,而C風格的轉(zhuǎn)換將失敗,異常。
// for single char strings, both are the same
assert ((char) "c").class==Character
assert ("c" as char).class==Character
// for multi char strings they are not
try {
((char) 'cx') == 'c'
assert false: 'will fail - not castable'
} catch(GroovyCastException e) {
}
assert ('cx' as char) == 'c'
assert 'cx'.asType(char) == 'c'
7、原始和封裝
因為Groovy使用Objects來做每一件事,它對原始的引用自動包裝。 因此,它不遵循Java的擴展優(yōu)先于裝箱。 這里有一個使用int的例子
int i
m(i)
//這是Java將調(diào)用的方法,因為擴展優(yōu)先于裝箱。
void m(long l) {
println "in m(long)"
}
//這是Groovy實際調(diào)用的方法,因為所有的基本引用都使用它們的包裝類。
void m(Integer i) {
println "in m(Integer)"
}
8、==的行為
在Java中==表示對象的原始類型或標識的相等性。 在Groovy ==翻譯為a.compareTo(b)== 0,如果他們是可比較的,否則a.equals(b)。 如果要檢查身份,有is方法,例如a.is(b)
三、基礎語法
1、動態(tài)類型
Groovy定義變量時:可以用Groovy風格的def聲明,不指定類型;也可以兼容Java風格,指定變量類型;甚至還可以省略def或類型。
def t1 = 't1'
String t2 = 't2'
t3 = 't3'
Groovy風格定義的變量類型是動態(tài)的,編譯成class時會自動轉(zhuǎn)換成正確的Java類型。
def var = 'text'
println var
var = 5
println var + 1
可用Java實現(xiàn)類似效果如下。
Object o = "text";
System.out.println(String.valueOf(o));
o = 5;
System.out.println(String.valueOf(Integer.valueOf(o) + 1));
2、字符串
Groovy支持靈活的字符串語法,例如:
// 單引號字符串
def a = 'hello "world"'
// 雙引號字符串
def b = "What's the weather like?"
// 用加號連接字符串,用等號對比字符串
assert 'ab' == 'a' + 'b'
// 三個單引號字符串,支持直接換行
def aMultilineString = '''line one
line two
line three'''
// 斜線字符串中,反斜線不需要轉(zhuǎn)義,常用于正則表達式
def fooPattern = /.*foo.*/
// 雙引號字符串支持用$嵌入變量
def name = 'Tom'
def greeting = "Hello ${name}"
// 如需函數(shù)調(diào)用,則$后表達式要加大括號
def pi = 3.14
def piString = "Pi = ${pi.toString()}"
3、閉包 (Closure)
閉包是一個變量,又是一個函數(shù),類似C語言中的函數(shù)指針,或者Java中只有一個方法的接口(Runnable等)。
反編譯class文件可以看出,Groovy閉包都會轉(zhuǎn)化為繼承g(shù)roovy.lang.Closure的類。
閉包方法的參數(shù)用箭頭定義,如果不特殊指定,則默認有一個it參數(shù)。
閉包方法的返回值可以用return顯示指定,如果不指定則使用最后一條語句的值。
def c1 = {
println 'hello'
}
def c2 = { a, b ->
println a
println b
}
def c3 = { int a, String b ->
println a
println b
}
def c4 = { ->
println 'hello'
}
def c5 = {
println it
}
def c6 = {
return it + 1
}
def c7 = {
it + 1
}
閉包調(diào)用可以用call,也可以直接像Java方法一樣加括號調(diào)用。
def c = {
println it
}
c.call('text1')
c('text2')
Java實現(xiàn)閉包效果:
abstract class MyClosure {
abstract void call(Object o);
}
MyClosure c = new MyClosure() {
@Override
void call(Object o) {
System.out.println(String.valueOf(o));
}
};
c.call("text");
4、方法/閉包的定義與調(diào)用
Groovy中定義方法既可以用Groovy閉包風格,也可以用Java風格,參數(shù)/返回值類型也是可選的。
def f1 = { text ->
println text
}
def f2(text) {
println text
}
void f3(String text) {
println text
}
注意函數(shù)定義不能這么寫,會被視為函數(shù)調(diào)用。
f4(text) {
println text
}
調(diào)用帶參數(shù)的閉包/函數(shù),通常可以省略括號,如果最后一個參數(shù)是閉包,還可以單獨寫在括號后面,如下。
println('hello')
println 'hello'
def func = { text, Closure closure ->
println text
closure.call()
}
func('1', {
println '2'
})
func '3', {
println '4'
}
func('5') {
println '6'
}
5、delegate,owner,this
查看Closure類的源碼,可以發(fā)現(xiàn)閉包中有delegate、owner、this三個成員變量,調(diào)用閉包沒有的屬性/方法時,會嘗試在這三個變量上調(diào)用。一般情況下:
this指向閉包外部的Object,指定義閉包的類。
owner指向閉包外部的Object/Closure,指直接包含閉包的類或閉包。
delegate默認和owner一致,指用于處理閉包屬性/方法調(diào)用的第三方對象,可以修改。
在閉包構(gòu)造時this和owner就已經(jīng)確定并傳入,是只讀的。如果需要修改,可以用Closure.rehydrate()方法克隆新的閉包,同時設置其this和owner。
Closure還有一個resolveStrategy屬性,有多種值(OWNER_FIRST、DELEGATE_FIRST、OWNER_ONLY、DELEGATE_ONLY、TO_SELF),默認為OWNER_FIRST,表示調(diào)用閉包沒有定義的屬性/方法時,先嘗試從owner取,再嘗試從delegate取。
Groovy代碼示例:
class MyDelegate {
def func = {
println('hello')
}
}
def c = {
func()
}
c.delegate = new MyDelegate()
c.call()
用Java實現(xiàn)類似效果如下。
static boolean callMethod(Object o, String method, Object... args) {
try {
Method func = o.getClass().getDeclaredMethod(method);
if (func != null) {
func.invoke(o, args);
return true;
}
} catch (Exception ignored) {
}
return false;
}
class MyDelegate {
void func() {
System.out.println("func");
}
}
abstract class MyClosure {
Object delegate;
abstract void call();
}
MyClosure c = new MyClosure() {
@Override
void call() {
if (!callMethod(this, "func")) {
callMethod(delegate, "func");
}
}
};
c.delegate = new MyDelegate();
c.call();
6、屬性與Getter、Setter
Groovy中對象的屬性(通常即成員變量)可以直接用名字訪問,實際上會調(diào)用getter和setter
// File沒有absolutePath的成員變量,但有g(shù)etAbsolutePath方法,可以直接當屬性訪問
println new File('text').absolutePath
// File沒有setAbsolutePath方法,這句會報ReadOnlyPropertyException
new File('text').absolutePath = '1'
四、集合操作
1、Lists
List 字面值
您可以按如下所示創(chuàng)建列表。 請注意,[]是空列表表達式。
def list = [5, 6, 7, 8]
assert list.get(2) == 7
assert list[2] == 7
assert list instanceof java.util.List
def emptyList = []
assert emptyList.size() == 0
emptyList.add(5)
assert emptyList.size() == 1
每個列表表達式都是創(chuàng)建[java.util.List],一個list可以用作構(gòu)造另一個list的源:
def list1 = ['a', 'b', 'c']
//構(gòu)造一個新的List,這個List和list1有相同的items
def list2 = new ArrayList<String>(list1)
assert list2 == list1 // == 檢測每一個對應的item,判斷它們是否相同
// clone() 也是可以使用的
def list3 = list1.clone()
assert list3 == list1
list是objects的有序集合:
def list = [5, 6, 7, 8]
assert list.size() == 4
assert list.getClass() == ArrayList //所使用的列表的具體類型
assert list[2] == 7 // 索引是從0開始的
assert list.getAt(2) == 7 // 同[]運算符
assert list.get(2) == 7 // 替代方法
list[2] = 9
assert list == [5, 6, 9, 8,] //結(jié)果通過
list.putAt(2, 10) //等效于 list[2] = 10
assert list == [5, 6, 10, 8]
assert list.set(2, 11) == 10 // 賦值并返回原值
assert list == [5, 6, 11, 8]
assert ['a', 1, 'a', 'a', 2.5, 2.5f, 2.5d, 'hello', 7g, null, 9 as byte]
//元素可以是不同類型; 允許重復
assert [1, 2, 3, 4, 5][-1] == 5 // 允許負數(shù)index,從list尾部開始計數(shù)
assert [1, 2, 3, 4, 5][-2] == 4
assert [1, 2, 3, 4, 5].getAt(-2) == 4 // getAt() 可以使用負數(shù)index
try {
[1, 2, 3, 4, 5].get(-2) // 但是get()方法不允許使用負數(shù)index
assert false
} catch (e) {
assert e instanceof ArrayIndexOutOfBoundsException
}
List迭代
迭代列表的元素通常是通過調(diào)用each和eachWithIndex方法,它們對列表的每個項執(zhí)行代碼:
[1, 2, 3].each {
println "Item: $it"http://it是對應于當前元素的隱式參數(shù)
}
['a', 'b', 'c'].eachWithIndex { it, i -> //it是當前元素, i是索引位置
println "$i: $it"
}
除了迭代之外,通過將每個元素轉(zhuǎn)換為其他元素來創(chuàng)建新的List通常是很有用的。 這個操作,通常稱為映射,在Groovy中通過collect方法完成:
assert [1, 2, 3].collect { it * 2 } == [2, 4, 6]
//簡潔語法
assert [1, 2, 3]*.multiply(2) == [1, 2, 3].collect { it.multiply(2) }
def list = [0]
//可以給“collect”傳入list參數(shù),收集元素的列表
assert [1, 2, 3].collect(list) { it * 2 } == [0, 2, 4, 6]
assert list == [0, 2, 4, 6]
list操作
過濾和搜索
Groovy開發(fā)工具包包含許多集合的方法,通過這些方法增強標準集合的功能,其中一些如下所示:
assert [1, 2, 3].find { it > 1 } == 2 // 找出第一個符合條件的元素
assert [1, 2, 3].findAll { it > 1 } == [2, 3] //找出所有符合條件的元素
assert ['a', 'b', 'c', 'd', 'e'].findIndexOf { // 找出符合條件的第一個元素的index
it in ['c', 'e', 'g']
} == 2
assert ['a', 'b', 'c', 'd', 'c'].indexOf('c') == 2 // 返回index
assert ['a', 'b', 'c', 'd', 'c'].indexOf('z') == -1 // index返回-1意味著沒有找到結(jié)果
assert ['a', 'b', 'c', 'd', 'c'].lastIndexOf('c') == 4
assert [1, 2, 3].every { it < 5 } // 如果每一個元素都符合條件則返回true
assert ![1, 2, 3].every { it < 3 }
assert [1, 2, 3].any { it > 2 } // 如果有一個元素符合條件就返回true
assert ![1, 2, 3].any { it > 3 }
assert [1, 2, 3, 4, 5, 6].sum() == 21 // 所有元素求和
assert ['a', 'b', 'c', 'd', 'e'].sum {
it == 'a' ? 1 : it == 'b' ? 2 : it == 'c' ? 3 : it == 'd' ? 4 : it == 'e' ? 5 : 0
// 求和的時候可以自定義元素的值
} == 15
assert ['a', 'b', 'c', 'd', 'e'].sum { ((char) it) - ((char) 'a') } == 10
assert ['a', 'b', 'c', 'd', 'e'].sum() == 'abcde'
assert [['a', 'b'], ['c', 'd']].sum() == ['a', 'b', 'c', 'd']
// 可以提供初始值
assert [].sum(1000) == 1000
assert [1, 2, 3].sum(1000) == 1006
assert [1, 2, 3].join('-') == '1-2-3' // 每個元素之間添加字符串
assert [1, 2, 3].inject('counting: ') { str, item ->
str + item // 減少操作
} == 'counting: 123'
assert [1, 2, 3].inject(0) { count, item ->
count + item
} == 6
這里是用于在集合中查找最大和最小值的慣用Groovy代碼:
def list = [9, 4, 2, 10, 5]
assert list.max() == 10
assert list.min() == 2
// 單字符的list也可以查找最大值和最小值
assert ['x', 'y', 'a', 'z'].min() == 'a'
// 我們可以用Closure閉包來描述元素的大小
def list2 = ['abc', 'z', 'xyzuvw', 'Hello', '321']
assert list2.max { it.size() } == 'xyzuvw'
assert list2.min { it.size() } == 'z'
除了閉包之外,您還可以使用Comparator來定義比較條件:
Comparator mc = { a, b -> a == b ? 0 : (a < b ? -1 : 1) }
def list = [7, 4, 9, -6, -1, 11, 2, 3, -9, 5, -13]
assert list.max(mc) == 11
assert list.min(mc) == -13
Comparator mc2 = { a, b -> a == b ? 0 : (Math.abs(a) < Math.abs(b)) ? -1 : 1 }
assert list.max(mc2) == -13
assert list.min(mc2) == -1
assert list.max { a, b -> a.equals(b) ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == -13
assert list.min { a, b -> a.equals(b) ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == -1
添加或刪除元素
我們可以使用[]分配一個新的空List,使用<<為List添加項目:
def list = []
assert list.empty
list << 5
assert list.size() == 1
list << 7 << 'i' << 11
assert list == [5, 7, 'i', 11]
list << ['m', 'o']
assert list == [5, 7, 'i', 11, ['m', 'o']]
//在<<表達式最前端的list是目標list
assert ([1, 2] << 3 << [4, 5] << 6) == [1, 2, 3, [4, 5], 6]
//使用leftShift方法等價于使用 <<
assert ([1, 2, 3] << 4) == ([1, 2, 3].leftShift(4))
我們可以通過多種方式添加到List中:
assert [1, 2] + 3 + [4, 5] + 6 == [1, 2, 3, 4, 5, 6]
// 等價于調(diào)用plus方法
assert [1, 2].plus(3).plus([4, 5]).plus(6) == [1, 2, 3, 4, 5, 6]
def a = [1, 2, 3]
a += 4 //創(chuàng)建了一個新的List
a += [5, 6]
assert a == [1, 2, 3, 4, 5, 6]
assert [1, *[222, 333], 456] == [1, 222, 333, 456]
assert [*[1, 2, 3]] == [1, 2, 3]
assert [1, [2, 3, [4, 5], 6], 7, [8, 9]].flatten() == [1, 2, 3, 4, 5, 6, 7, 8, 9]
def list = [1, 2]
list.add(3)
list.addAll([5, 4])
assert list == [1, 2, 3, 5, 4]
list = [1, 2]
list.add(1, 3) //在索引1前面插入元素3
assert list == [1, 3, 2]
list.addAll(2, [5, 4]) //在索引2前面插入元素[5,4]
assert list == [1, 3, 5, 4, 2]
list = ['a', 'b', 'z', 'e', 'u', 'v', 'g']
list[8] = 'x' // []運算符根據(jù)需要使列表增長
// 如果需要插入null
assert list == ['a', 'b', 'z', 'e', 'u', 'v', 'g', null, 'x']
然而,重要的是List上的+運算符不會改變List本身。 與<<相比,+運算符會創(chuàng)建一個新的列表,這通常不是你想要的,并可能導致性能問題。
Groovy開發(fā)包還包含一些方法,使您可以通過元素值輕松地從列表中刪除元素:
assert ['a','b','c','b','b'] - 'c' == ['a','b','b','b']
assert ['a','b','c','b','b'] - 'b' == ['a','c']
assert ['a','b','c','b','b'] - ['b','c'] == ['a']
def list = [1,2,3,4,3,2,1]
list -= 3 //從原始list創(chuàng)建一個新的list,并刪除元素3
assert list == [1,2,4,2,1]
assert ( list -= [2,4] ) == [1,1]
也可以通過引用其索引來刪除元素,在這種情況下,列表會改變:
def list = [1,2,3,4,5,6,2,2,1]
assert list.remove(2) == 3 //刪除第三個元素并返回第三個元素的值
assert list == [1,2,4,5,6,2,2,1]
如果你只想刪除列表中具有相同值的第一個元素,而不是刪除所有元素,則調(diào)用remove方法:
def list= ['a','b','c','b','b']
assert list.remove('c') // 刪除元素'c'如果刪除成功返回true
assert list.remove('b') // 刪除第一個找到的元素'b',如果刪除成功返回true
assert ! list.remove('z') // 返回false,因為沒有任何元素刪除
assert list == ['a','b','b']
刪除列表中的所有元素可以通過調(diào)用clear方法來完成:
def list= ['a',2,'c',4]
list.clear()
assert list == []
設置操作
Groovy開發(fā)工具包還包括一些方法,使得它易于推理:
assert 'a' in ['a','b','c'] // 如果元素'a'在list中返回true
assert ['a','b','c'].contains('a') // 等價于java中的`contains`方法
assert [1,3,4].containsAll([1,4]) // `containsAll` 將檢測每一個待查元素,如果都包含在list中,返回true
assert [1,2,3,3,3,3,4,5].count(3) == 4 // 返回元素3在列表中包含的數(shù)量
assert [1,2,3,3,3,3,4,5].count {
it%2==0 // 返回符合斷言的元素在列表中包含的數(shù)量
} == 2
assert [1,2,4,6,8,10,12].intersect([1,3,6,9,12]) == [1,6,12]//返回兩個列表的交集
assert [1,2,3].disjoint( [4,6,9] )//兩個列表是互斥的,返回true
assert ![1,2,3].disjoint( [2,4,6] )
排序
使用List經(jīng)常會遇到排序。 Groovy提供了各種選項來排序List,從使用閉包到comparators,如下例所示:
assert [6, 3, 9, 2, 7, 1, 5].sort() == [1, 2, 3, 5, 6, 7, 9]
def list = ['abc', 'z', 'xyzuvw', 'Hello', '321']
assert list.sort {
it.size()
} == ['z', 'abc', '321', 'Hello', 'xyzuvw']
def list2 = [7, 4, -6, -1, 11, 2, 3, -9, 5, -13]
assert list2.sort { a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } ==
[-1, 2, 3, 4, 5, -6, 7, -9, 11, -13]
Comparator mc = { a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 }
// 僅限于JDK 8+
// list2.sort(mc)
// assert list2 == [-1, 2, 3, 4, 5, -6, 7, -9, 11, -13]
def list3 = [6, -3, 9, 2, -7, 1, 5]
Collections.sort(list3)
assert list3 == [-7, -3, 1, 2, 5, 6, 9]
Collections.sort(list3, mc)
assert list3 == [1, 2, -3, 5, 6, -7, 9]
復制元素
Groovy開發(fā)工具包還利用了操作符重載,以提供允許復制列表元素的方法:
assert [1, 2, 3] * 3 == [1, 2, 3, 1, 2, 3, 1, 2, 3]
assert [1, 2, 3].multiply(2) == [1, 2, 3, 1, 2, 3]
assert Collections.nCopies(3, 'b') == ['b', 'b', 'b']
// 來自JDK的nCopies具有與列表的乘法不同的語義
assert Collections.nCopies(2, [1, 2]) == [[1, 2], [1, 2]] //而不是[1,2,1,2]
2、Maps
Map 字面值
在Groovy中,可以使用map語法創(chuàng)建map(也稱為關(guān)聯(lián)數(shù)組):[:]:
def map = [name: 'Gromit', likes: 'cheese', id: 1234]
assert map.get('name') == 'Gromit'
assert map.get('id') == 1234
assert map['name'] == 'Gromit'
assert map['id'] == 1234
assert map instanceof java.util.Map
def emptyMap = [:]
assert emptyMap.size() == 0
emptyMap.put("foo", 5)
assert emptyMap.size() == 1
assert emptyMap.get("foo") == 5
默認情況下,map的key是字符串:[a:1]等價于['a':1]。 如果您定義一個名為a的變量,并且您希望a的值為您的map的key,這可能會令人困惑。 如果是這種情況,則必須通過添加括號來轉(zhuǎn)義鍵,如以下示例所示:
def a = 'Bob'
def ages = [a: 43]
assert ages['Bob'] == null // 找不到`Bob`
assert ages['a'] == 43 // 因為 `a` 是字面值(文本)!
ages = [(a): 43] // 將`a`用()括起來
assert ages['Bob'] == 43 // 這時就能找到'Bob'的值了
除了map 字面值外, 也可以通過clone方法得到一個map的副本:
def map = [
simple : 123,
complex: [a: 1, b: 2]
]
def map2 = map.clone()
assert map2.get('simple') == map.get('simple')
assert map2.get('complex') == map.get('complex')
map2.get('complex').put('c', 3)
assert map.get('complex').get('c') == 3
如前面的示例所示,生成的map是原始map的淺拷貝
Map屬性符號
Maps也像bean一樣,所以你可以使用屬性符號在Map中g(shù)et/set項,只要鍵是有效的Groovy標識符的字符串即可:
def map = [name: 'Gromit', likes: 'cheese', id: 1234]
assert map.name == 'Gromit' // 同map.get('Gromit')
assert map.id == 1234
def emptyMap = [:]
assert emptyMap.size() == 0
emptyMap.foo = 5
assert emptyMap.size() == 1
assert emptyMap.foo == 5
注意:根據(jù)設計,map.foo會一直在map中查找這個key。這意味著foo.class將在不包含class鍵的map上返回null。如果你是想返回foo的類型,那么你必須使用foo.getClass()方法:
def map = [name: 'Gromit', likes: 'cheese', id: 1234]
assert map.class == null
assert map.get('class') == null
assert map.getClass() == LinkedHashMap // 這很可能是你想要的結(jié)果
map = [1 : 'a',
(true) : 'p',
(false): 'q',
(null) : 'x',
'null' : 'z']
assert map.containsKey(1) // 數(shù)字1不是一個標識符,所以得這樣調(diào)用
assert map.true == null
assert map.false == null
assert map.get(true) == 'p'
assert map.get(false) == 'q'
assert map.null == 'z'
assert map.get(null) == 'x'
Map迭代
照例,在Groovy開發(fā)工具包中,Map上的慣用迭代使用each和eachWithIndex方法。 值得注意的是,使用Map字面值符號創(chuàng)建的Map是有序的,也就是說,如果對Map條目進行迭代,將保證條目將按照它們在Map中添加的順序返回。
def map = [
Bob : 42,
Alice: 54,
Max : 33
]
// `entry` is a map entry
map.each { entry ->
println "Name: $entry.key Age: $entry.value"
}
// `entry` is a map entry, `i` the index in the map
map.eachWithIndex { entry, i ->
println "$i - Name: $entry.key Age: $entry.value"
}
// Alternatively you can use key and value directly
map.each { key, value ->
println "Name: $key Age: $value"
}
// Key, value and i as the index in the map
map.eachWithIndex { key, value, i ->
println "$i - Name: $key Age: $value"
}
Map操作
添加或刪除元素
向Map中添加元素可以使用put方法,下標運算符或使用putAll:
def defaults = [1: 'a', 2: 'b', 3: 'c', 4: 'd']
def overrides = [2: 'z', 5: 'x', 13: 'x']
def result = new LinkedHashMap(defaults)
result.put(15, 't')
result[17] = 'u'
result.putAll(overrides)
assert result == [1: 'a', 2: 'z', 3: 'c', 4: 'd', 5: 'x', 13: 'x', 15: 't', 17: 'u']
刪除Map的所有元素可以通過調(diào)用clear方法來完成:
def m = [1:'a', 2:'b']
assert m.get(1) == 'a'
m.clear()
assert m == [:]
使用Map字面值語法生成的Map使用object的equals和hashcode方法。 這意味著你不應該使用哈希碼隨時間變化的object,否則你將無法獲得相關(guān)的值。
還值得注意的是,您不應該使用GString作為Map的鍵,因為GString的哈希碼與等效String的哈希碼不同:
def key = 'some key'
def map = [:]
def gstringKey = "${key.toUpperCase()}"
map.put(gstringKey,'value')
assert map.get('SOME KEY') == null
Keys, values and entries
我們可以檢查視圖中的keys, values, and entries:
def map = [1:'a', 2:'b', 3:'c']
def entries = map.entrySet()
entries.each { entry ->
assert entry.key in [1,2,3]
assert entry.value in ['a','b','c']
}
def keys = map.keySet()
assert keys == [1,2,3] as Set
由于操作的成功直接取決于正在操作的Map的類型,因此視圖(不管是map的entry,key還是value)返回的變化值是非常不鼓勵的。 特別地,Groovy依賴于來自JDK的集合,通常不能保證集合可以通過keySet,entrySet或values安全地操作。
過濾和搜索
Groovy開發(fā)包包含與List中類似的過濾,搜索和收集方法:
def people = [
1: [name:'Bob', age: 32, gender: 'M'],
2: [name:'Johnny', age: 36, gender: 'M'],
3: [name:'Claire', age: 21, gender: 'F'],
4: [name:'Amy', age: 54, gender:'F']
]
def bob = people.find { it.value.name == 'Bob' } // 查找單個entry
def females = people.findAll { it.value.gender == 'F' }
//兩個都是返回entries,但是您可以使用collect來檢索年齡例如
def ageOfBob = bob.value.age
def agesOfFemales = females.collect {
it.value.age
}
assert ageOfBob == 32
assert agesOfFemales == [21,54]
// 但您也可以使用鍵/對值作為閉包的參數(shù)but you could also use a key/pair value as the parameters of the closures
def agesOfMales = people.findAll { id, person ->
person.gender == 'M'
}.collect { id, person ->
person.age
}
assert agesOfMales == [32, 36]
// `every` 如果所有entries都匹配規(guī)則,則返回true
assert people.every { id, person ->
person.age > 18
}
// `any` 如果任何一個entry匹配規(guī)則,則返回true
assert people.any { id, person ->
person.age == 54
}
分組
我們可以使用一些標準將List分組到Map中:
assert ['a', 7, 'b', [2, 3]].groupBy {
it.class
} == [(String) : ['a', 'b'],
(Integer) : [7],
(ArrayList): [[2, 3]]
]
assert [
[name: 'Clark', city: 'London'], [name: 'Sharma', city: 'London'],
[name: 'Maradona', city: 'LA'], [name: 'Zhang', city: 'HK'],
[name: 'Ali', city: 'HK'], [name: 'Liu', city: 'HK'],
].groupBy { it.city } == [
London: [[name: 'Clark', city: 'London'],
[name: 'Sharma', city: 'London']],
LA : [[name: 'Maradona', city: 'LA']],
HK : [[name: 'Zhang', city: 'HK'],
[name: 'Ali', city: 'HK'],
[name: 'Liu', city: 'HK']],
]
3、Ranges
Ranges允許您創(chuàng)建順序值List。 這些可以用作List,因為Range擴展了java.util.List。
使用..符號定義的范圍是包含性的(即List包含from和to值)。
使用.. <符號定義的范圍是半開的,它們包括第一個值,但不是最后一個值。
// 全包含的range
def range = 5..8
assert range.size() == 4
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert range.contains(8)
// 半開的range
range = 5..<8
assert range.size() == 3
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert !range.contains(8)
//獲取range的端點而不使用索引
range = 1..10
assert range.from == 1
assert range.to == 10
請注意,int類型的Range實現(xiàn)了高效率,創(chuàng)建了一個包含from和to值的輕量級Java對象。
Ranges可以用于實現(xiàn)java.lang.Comparable以進行比較的任何Java對象,并且還有方法next()和previous()返回range中的下一個/上一個項目。 例如,您可以創(chuàng)建一系列String元素:
// 全包括range
def range = 'a'..'d'
assert range.size() == 4
assert range.get(2) == 'c'
assert range[2] == 'c'
assert range instanceof java.util.List
assert range.contains('a')
assert range.contains('d')
assert !range.contains('e')
您可以使用經(jīng)典的for循環(huán)在一個range上進行迭代:
for (i in 1..10) {
println "Hello ${i}"
}
但是或者,您可以通過使用each方法迭代Range,在更加Groovy慣用的風格中實現(xiàn)相同的效果:
(1..10).each { i ->
println "Hello ${i}"
}
Range也可以用在switch語句中:
switch (years) {
case 1..10: interestRate = 0.076; break;
case 11..25: interestRate = 0.052; break;
default: interestRate = 0.037;
}
4、集合的語法增強
GPath支持
由于對List和Map的屬性符號支持,Groovy提供了語法糖,使得處理嵌套集合變得非常容易,如下例所示:
def listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22]]
assert listOfMaps.a == [11, 21] //GPath 標記
assert listOfMaps*.a == [11, 21] //擴展點符號
listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22], null]
assert listOfMaps*.a == [11, 21, null] // 適用于空值
assert listOfMaps*.a == listOfMaps.collect { it?.a } //等價符號
// 但這只會收集非空值
assert listOfMaps.a == [11,21]
擴展運算符
擴展運算符可以用于將集合“內(nèi)聯(lián)”到另一個集合中。 它是語法糖,通常避免調(diào)用putAll并促進一行的實現(xiàn):
assert [ 'z': 900,
*: ['a': 100, 'b': 200], 'a': 300] == ['a': 300, 'b': 200, 'z': 900]
//在map定義中的擴展map符號
assert [*: [3: 3, *: [5: 5]], 7: 7] == [3: 3, 5: 5, 7: 7]
def f = { [1: 'u', 2: 'v', 3: 'w'] }
assert [*: f(), 10: 'zz'] == [1: 'u', 10: 'zz', 2: 'v', 3: 'w']
//函數(shù)參數(shù)中的擴展map符號
f = { map -> map.c }
assert f(*: ['a': 10, 'b': 20, 'c': 30], 'e': 50) == 30
f = { m, i, j, k -> [m, i, j, k] }
//使用具有混合未命名和命名參數(shù)的展開map符號
assert f('e': 100, *[4, 5], *: ['a': 10, 'b': 20, 'c': 30], 6) ==
[["e": 100, "b": 20, "c": 30, "a": 10], 4, 5, 6]
星號“*”運算符
“星點”運算符是一個快捷運算符,允許您對集合的所有元素調(diào)用方法或?qū)傩裕?/p>
assert [1, 3, 5] == ['a', 'few', 'words']*.size()
class Person {
String name
int age
}
def persons = [new Person(name:'Hugo', age:17), new Person(name:'Sandra',age:19)]
assert [17, 19] == persons*.age
使用下標運算符切片
您可以使用下標表達式將其索引到list,數(shù)組和map中。 有趣的是,字符串在這種情況下被視為特殊類型的集合:
def text = 'nice cheese gromit!'
def x = text[2]
assert x == 'c'
assert x.class == String
def sub = text[5..10]
assert sub == 'cheese'
def list = [10, 11, 12, 13]
def answer = list[2,3]
assert answer == [12,13]
請注意,您可以使用Range提取集合的一部分:
list = 100..200
sub = list[1, 3, 20..25, 33]
assert sub == [101, 103, 120, 121, 122, 123, 124, 125, 133]
下標運算符可用于更新現(xiàn)有集合(對于不可變的集合類型):
list = ['a','x','x','d']
list[1..2] = ['b','c']
assert list == ['a','b','c','d']
值得注意的是,允許使用負索引,從集合的末尾更容易提?。?br> 您可以使用負指數(shù)從List,array,String等結(jié)尾計數(shù)。
text = "nice cheese gromit!"
x = text[-1]
assert x == "!"
def name = text[-7..-2]
assert name == "gromit"
最終,如果使用向后Range(開始索引大于結(jié)束索引),則答案將反過來。
text = "nice cheese gromit!"
name = text[3..1]
assert name == "eci"
五、IO操作
Groovy為I/O操作提供了許多幫助方法,雖然你可以在Groovy中用標準Java代碼來實現(xiàn)I/O操作,不過Groovy提供了大量的方便的方式來操作File、Stream、Reader等等。
1、讀取文件
讀取文本文件并打印每一行文本
new File(baseDir, 'haiku.txt').eachLine{ line ->
println line
}
eachLine方法是Groovy為File類自動添加的方法,同時提供多個變體方法,比如你想知道讀取的行數(shù),你可以使用它的變體方法,如下
new File(baseDir, 'haiku.txt').eachLine{ line, nb ->
println "Line $nb: $line"
}
如果出于某種原因eachLine拋出異常,該方法能夠確保資源正確地關(guān)閉,這適用于所有Groovy添加的I/O資源方法
例如在某些情況下,你更喜歡使用Reader訪問I/O資源,但你仍然受益于Groovy的自動資源管理。在下一個示例中,即使發(fā)生了異常,Reader也將會被關(guān)閉。
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')
}
}
}
如果你需要收集文本文件的每一行到一個列表中,你可以這樣做:
def list = new File(baseDir, 'haiku.txt').collect {it}
或者你甚至可以用as操作符來講文件內(nèi)容轉(zhuǎn)為String數(shù)組
def array = new File(baseDir, 'haiku.txt') as String[]
多少次你不得不獲得一個文件的內(nèi)容到一個byte[],它需要多少代碼?Groovy使得這件事非常的容易。
byte[] contents = file.bytes
處理I/O并不局限于處理文件。事實上,很多的操作依賴于輸入/輸出流,這就是為什么Groovy添加大量的支持方法,正如你所看到的文檔。
例如,你可以很容易從一個文件獲得一個InputStream
def is = new File(baseDir,'haiku.txt').newInputStream()
// do something ...
is.close()
但是你可以看到,這種方式需要你手動關(guān)閉流。會為你留意到,在Groovy中使用withInputStream通常是更好的方式。
new File(baseDir,'haiku.txt').withInputStream { stream ->
// do something ...
}
2、寫文件
當然,一些時候你并不想讀取文件,而是要寫文件。其中一個方式就是使用Writer:
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!'''
當然我們并不總是處理文本內(nèi)容,所以您可以使用Writer或者直接寫入字節(jié),例如:
file.bytes = [66,22,11]
當然你也可以直接處理輸出流。例如,下面就是如何創(chuàng)建一個輸出流寫入一個文件:
def os = new File(baseDir,'data.bin').newOutputStream()
// do something ...
os.close()
但是你可以看到它需要你手動關(guān)閉輸出流。會為你留意到,在Groovy中使用withOutputStream通常是更好的方式。
new File(baseDir,'data.bin').withOutputStream { stream ->
// do something ...
}
3、遍歷文件樹
在腳本中,在文件樹種查找一些特定文件并處理這些文件是很常見的事情。Groovy提供了多種方法來做到這一點。例如你可以為一個文件夾中的每個文件直行一些操作:
//在目錄的每一個文件直行閉包代碼
dir.eachFile { file ->
println file.name
}
//在目錄中符合匹配模式的文件直行閉包代碼
dir.eachFileMatch(~/.*\.txt/) { file ->
println file.name
}
通常你需要處理一個更深的目錄結(jié)構(gòu)的文件,這種情況下你可以使用eachFileRecurse
//在指定目錄遞歸查找,并在每一個文件和目錄直行閉包代碼
dir.eachFileRecurse { file ->
println file.name
}
//在指定目錄遞歸查找,并在每一個文件直行閉包代碼
dir.eachFileRecurse(FileType.FILES) { file ->
println file.name
}
對于更復雜的遍歷技術(shù)可以使用traverse方法,它你需要設置一個特殊的標志指示這個遍歷要做什么。
dir.traverse { file ->
if (file.directory && file.name=='bin') {
//如果當前file是一個目錄或者它的名字是 bin ,則停止遍歷
FileVisitResult.TERMINATE
} else {
//打印文件名并繼續(xù)遍歷
println file.name
FileVisitResult.CONTINUE
}
}
4、數(shù)據(jù)和對象
在Java中使用java.io.DataOutputStream和java.io.DataInputStream進行序列化和反序列化并不少見,Groovy將使它更容易處理。例如,您可以把數(shù)據(jù)序列化到一個文件中并反序列化它使用以下代碼:
boolean b = true
String message = 'Hello from Groovy'
// Serialize data into a file
file.withDataOutputStream { out ->
out.writeBoolean(b)
out.writeUTF(message)
}
// ...
// Then read it back
file.withDataInputStream { input ->
assert input.readBoolean() == b
assert input.readUTF() == message
}
類似的,如果您想要序列化的數(shù)據(jù)實現(xiàn)了Serializable接口,你可以將它作為一個Object輸出流處理,例如:
Person p = new Person(name:'Bob', age:76)
// Serialize data into a file
file.withObjectOutputStream { out ->
out.writeObject(p)
}
// ...
// Then read it back
file.withObjectInputStream { input ->
def p2 = input.readObject()
assert p2.name == p.name
assert p2.age == p.age
}
5、執(zhí)行外部程序
前一節(jié)中描述的是使用Groovy處理文件多么容易。然而在系統(tǒng)管理等領(lǐng)域或devops通常需要與外部processes溝通。Groovy提供了一個簡單的方法來執(zhí)行命令行processes。僅僅需要些命令字符串再調(diào)用execute()方法即可。如。在 *nix機(安裝了合適的 *nix命令工具的windows機),你這樣執(zhí)行:
//在外部process執(zhí)行`ls`命令
def process = "ls -l".execute()
//讀取文本并打印
println "Found text ${process.text}"
execute()方法返回一個java.lang.Process對象,并允許處理它的in/out/err流和退出值,從Process檢查等。
如,這是和上面一樣的命令
//執(zhí)行`ls`命令
def process = "ls -l".execute()
//遍歷命令進程的輸入流
process.in.eachLine { line ->
//打印每一行輸入流
println line
}
值得注意的是,in是一個輸入流,對應于命令的標準輸出。 out指的是可以向Process發(fā)送數(shù)據(jù)的流(標準輸入)。
記住許多命令是shell內(nèi)置函數(shù),需要特殊處理。所以,如果你想要在一個Windows機器上列出一個目錄下的所有文件:
def process = "dir".execute()
println "${process.text}"
你會收到一個IOException說無法運行程序“dir”:CreateProcess error = 2,系統(tǒng)找不到指定的文件。這是因為dir內(nèi)置在Windows shell(cmd.exe)中,不能作為簡單的可執(zhí)行文件運行。 相反,你需要寫:
def process = "cmd /c dir".execute()
println "${process.text}"
此外,由于此功能當前使用java.lang.Process底層,必須考慮該類的缺陷。尤其是,這個類的javadoc說:
由于一些本地平臺僅為標準輸入和輸出流提供有限的緩沖區(qū)大小,因此無法及時寫入輸入流或讀取子過程的輸出流可能導致子過程阻塞甚至死鎖
正因為如此,Groovy提供了一些額外的幫助方法,使得流處理更容易。
這里是如何從你的Process中gobble(未翻譯)所有的輸出(包括錯誤流輸出):
def p = "rm -f foo.tmp".execute([], tmpDir)
p.consumeProcessOutput()
p.waitFor()
還有consumeProcessOutput的變體,使用StringBuffer,InputStream,OutputStream等...有關(guān)完整的列表,請閱讀GDK API for java.lang.Process
此外,這些是一個pipeTo命令(映射到|以允許重載),它使一個進程的輸出流被傳送到另一個進程的輸入流。
這里有一些使用的例子:
proc1 = 'ls'.execute()
proc2 = 'tr -d o'.execute()
proc3 = 'tr -d e'.execute()
proc4 = 'tr -d i'.execute()
proc1 | proc2 | proc3 | proc4
proc4.waitFor()
if (proc4.exitValue()) {
println proc4.err.text
} else {
println proc4.text
}
消耗錯誤
def sout = new StringBuilder()
def serr = new StringBuilder()
proc2 = 'tr -d o'.execute()
proc3 = 'tr -d e'.execute()
proc4 = 'tr -d i'.execute()
proc4.consumeProcessOutput(sout, serr)
proc2 | proc3 | proc4
[proc2, proc3].each { it.consumeProcessErrorStream(serr) }
proc2.withWriter { writer ->
writer << 'testfile.groovy'
}
proc4.waitForOrKill(1000)
println "Standard output: $sout"
println "Standard error: $serr"
六、運行時元編程
Groovy語言支持兩種類型的元編程:運行時元編程和編譯時元編程。 第一個允許在運行時改變類模型和程序的行為,而第二個只在編譯時發(fā)生。 兩者都有利弊,我們將在本節(jié)詳細介紹。
使用運行時元編程,我們可以在運行時截取,注入,合成類和接口的方法。 為了更深入地理解Groovy MOP,我們需要了解Groovy對象和Groovy的方法處理。 在Groovy中,我們使用了三種對象:POJO,POGO和Groovy Interceptors。 Groovy允許對所有類型的對象進行元編程,但是是以不同的方式進行的。
- POJO - 一個常規(guī)的Java對象,它的類是用Java或任何其他運行在JVM上的編程語言編寫的。
- POGO - 一個Groovy對象,它的類是用Groovy編寫的。 它擴展了java.lang.Object并在默認情況下實現(xiàn)了groovy.lang.GroovyObject接口。
- Groovy Interceptor - 一個Groovy對象,它實現(xiàn)了groovy.lang.GroovyInterceptable接口并具有方法攔截功能,我們將在GroovyInterceptable部分討論。
對于每個方法調(diào)用,Groovy檢查對象是POJO還是POGO。 對于POJO,Groovy從groovy.lang.MetaClassRegistry獲取它的MetaClass,并將方法調(diào)用委托給它。 對于POGO,Groovy需要更多步驟,如下圖所示:

1、GroovyObject接口
groovy.lang.GroovyObject是Groovy中的主要接口,就像Object類是Java中的主要接口一樣。GroovyObject在groovy.lang.GroovyObjectSupport類中有一個默認實現(xiàn),它負責將調(diào)用傳遞給groovy.lang.MetaClass對象。 GroovyObject源代碼如下:
public interface GroovyObject {
/**
* Invokes the given method.
*
* @param name the name of the method to call
* @param args the arguments to use for the method call
* @return the result of invoking the method
*/
Object invokeMethod(String name, Object args);
/**
* Retrieves a property value.
*
* @param propertyName the name of the property of interest
* @return the given property
*/
Object getProperty(String propertyName);
/**
* Sets the given property to the new value.
*
* @param propertyName the name of the property of interest
* @param newValue the new value for the property
*/
void setProperty(String propertyName, Object newValue);
/**
* Returns the metaclass for a given class.
*
* @return the metaClass of this instance
*/
MetaClass getMetaClass();
/**
* Allows the MetaClass to be replaced with a derived implementation.
*
* @param metaClass the new metaclass
*/
void setMetaClass(MetaClass metaClass);
}
invokeMethod
根據(jù)運行時元編程中的模式,當您調(diào)用的方法不存在于Groovy對象上時,將調(diào)用此方法。 下面是一個重寫invokeMethod()方法的簡單示例:
class SomeGroovyClass {
def invokeMethod(String name, Object args) {
return "called invokeMethod $name $args"
}
def test() {
return 'method exists'
}
}
def someGroovyClass = new SomeGroovyClass()
assert someGroovyClass.test() == 'method exists'
assert someGroovyClass.someMethod() == 'called invokeMethod someMethod []'
get/setProperty
對屬性的每個讀取訪問都可以通過覆蓋當前對象的getProperty()方法來攔截。 這里有一個簡單的例子:
class SomeGroovyClass {
def property1 = 'ha'
def field2 = 'ho'
def field4 = 'hu'
def getField1() {
return 'getHa'
}
def getProperty(String name) {
if (name != 'field3')
return metaClass.getProperty(this, name) //將請求轉(zhuǎn)發(fā)到getter以獲取除field3之外的所有屬性。
else
return 'field3'
}
}
def someGroovyClass = new SomeGroovyClass()
assert someGroovyClass.field1 == 'getHa'
assert someGroovyClass.field2 == 'ho'
assert someGroovyClass.field3 == 'field3'
assert someGroovyClass.field4 == 'hu'
您可以通過覆蓋setProperty()方法來攔截對屬性的寫訪問權(quán)限:
class POGO {
String property
void setProperty(String name, Object value) {
this.@"$name" = 'overridden'
}
}
def pogo = new POGO()
pogo.property = 'a'
assert pogo.property == 'overridden'
get/setMetaClass
您可以訪問對象的metaClass或設置您自己的MetaClass實現(xiàn)以更改默認攔截機制。 例如,您可以編寫自己的MetaClass接口實現(xiàn),并將其分配給對象,從而更改攔截機制:
// getMetaclass
someObject.metaClass
// setMetaClass
someObject.metaClass = new OwnMetaClassImplementation()
2、get/setAttribute
此功能與MetaClass實現(xiàn)相關(guān)。 在默認實現(xiàn)中,您可以訪問字段而不調(diào)用它們的getter和setter。 下面的示例演示了這種方法:
class SomeGroovyClass {
def field1 = 'ha'
def field2 = 'ho'
def getField1() {
return 'getHa'
}
}
def someGroovyClass = new SomeGroovyClass()
assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field1') == 'ha'
assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field2') == 'ho'
class POGO {
private String field
String property1
void setProperty1(String property1) {
this.property1 = "setProperty1"
}
}
def pogo = new POGO()
pogo.metaClass.setAttribute(pogo, 'field', 'ha')
pogo.metaClass.setAttribute(pogo, 'property1', 'ho')
assert pogo.field == 'ha'
assert pogo.property1 == 'ho'
3、methodMissing
Groovy支持methodMissing的概念。 此方法與invokeMethod的不同之處在于,它僅在失敗的方法分派的情況下被調(diào)用,當沒有找到給定名稱和/或給定參數(shù)的方法時:
class Foo {
def methodMissing(String name, def args) {
return "this is me"
}
}
assert new Foo().someUnknownMethod(42l) == 'this is me'
通常當使用methodMissing時,可以緩存結(jié)果,以便下次調(diào)用相同的方法。
例如,考慮GORM中的動態(tài)查找器。 這些是根據(jù)methodMissing來實現(xiàn)的。 代碼類似這樣:
class GORM {
def dynamicMethods = [...] // an array of dynamic methods that use regex
def methodMissing(String name, args) {
def method = dynamicMethods.find { it.match(name) }
if(method) {
GORM.metaClass."$name" = { Object[] varArgs ->
method.invoke(delegate, name, varArgs)
}
return method.invoke(delegate,name, args)
}
else throw new MissingMethodException(name, delegate, args)
}
}
注意如果我們找到一個方法來調(diào)用,隨后就會使用ExpandoMetaClass動態(tài)地注冊一個新的方法。 這是為了下次更有效率的調(diào)用相同的方法。 這種使用methodMissing的方法不像invokeMethod一樣,這種方式在第二次調(diào)用時并不產(chǎn)生昂貴開銷。
4、propertyMissing
Groovy支持propertyMissing的概念,用于攔截那些失敗的屬性解析嘗試。 在getter方法的情況下,propertyMissing接受一個包含屬性名稱的String參數(shù):
class Foo {
def propertyMissing(String name) { name }
}
assert new Foo().boo == 'boo'
僅當Groovy運行時找不到給定屬性的getter方法時才調(diào)用propertyMissing(String)方法。
對于setter方法,可以添加另一個propertyMissing定義,它接受一個附加的value參數(shù):
class Foo {
def storage = [:]
def propertyMissing(String name, value) { storage[name] = value }
def propertyMissing(String name) { storage[name] }
}
def f = new Foo()
f.foo = "bar"
assert f.foo == "bar"
與methodMissing一樣,最佳做法是在運行時動態(tài)注冊新屬性,以提高總體查找性能。
methodMissing和propertyMissing方法處理靜態(tài)方法和屬性可以通過ExpandoMetaClass
5、GroovyInterceptable
groovy.lang.GroovyInterceptable接口是用于通知Groovy運行時的擴展GroovyObject的標記接口,所有方法都應通過Groovy運行時的方法分派器機制攔截。
package groovy.lang;
public interface GroovyInterceptable extends GroovyObject {
}
當Groovy對象實現(xiàn)GroovyInterceptable接口時,對任何方法的調(diào)用都會調(diào)用invokeMethod()方法。 下面你可以看到一個這種類型的對象的簡單例子:
class Interception implements GroovyInterceptable {
def definedMethod() { }
def invokeMethod(String name, Object args) {
'invokedMethod'
}
}
下一段代碼是一個測試,顯示對現(xiàn)有和不存在的方法的調(diào)用都將返回相同的值。
class InterceptableTest extends GroovyTestCase {
void testCheckInterception() {
def interception = new Interception()
assert interception.definedMethod() == 'invokedMethod'
assert interception.someMethod() == 'invokedMethod'
}
}
我們不能使用默認groovy方法,如println,因為這些方法被注入到所有g(shù)roovy對象中,所以它們也會被攔截。
如果我們要攔截所有方法調(diào)用,但不想實現(xiàn)GroovyInterceptable接口,我們可以在對象的MetaClass上實現(xiàn)invokeMethod()。 此方法適用于POGO和POJO,如此示例所示:
class InterceptionThroughMetaClassTest extends GroovyTestCase {
void testPOJOMetaClassInterception() {
String invoking = 'ha'
invoking.metaClass.invokeMethod = { String name, Object args ->
'invoked'
}
assert invoking.length() == 'invoked'
assert invoking.someMethod() == 'invoked'
}
void testPOGOMetaClassInterception() {
Entity entity = new Entity('Hello')
entity.metaClass.invokeMethod = { String name, Object args ->
'invoked'
}
assert entity.build(new Object()) == 'invoked'
assert entity.someMethod() == 'invoked'
}
}
6、MateClass
Groovy帶有一個特殊的MetaClass,它就是ExpandoMetaClass。 它是特別的,它允許通過使用一個整潔的閉包語法動態(tài)添加或更改方法,構(gòu)造函數(shù),屬性,甚至靜態(tài)方法。
應用這些修改在模擬(mock)或樁(stub)情況下特別有用,如測試指南中所示。
每個java.lang.Class由Groovy提供,并有一個特殊的metaClass屬性,它將提供對ExpandoMetaClass實例的引用。 然后,此實例可用于添加方法或更改已有現(xiàn)有方法的行為。
默認情況下ExpandoMetaClass不執(zhí)行繼承。 要啟用它,您必須在應用程序啟動之前調(diào)用ExpandoMetaClass#enableGlobally(),例如在main方法或servlet引導中。
以下部分詳細介紹了ExpandoMetaClass如何在各種情況下使用。
方法
一旦通過調(diào)用metaClass屬性訪問ExpandoMetaClass,可以使用左移<<或=運算符來添加方法。
注意,左移位運算符用于附加一個新方法。 如果類或接口聲明了具有相同名稱和參數(shù)類型的公共方法,包括繼承自父類和父接口但不包括在運行時添加到metaClass的那些方法,那么將拋出異常。 如果要替換由類或接口聲明的方法,可以使用=運算符。
運算符通過一個Closure代碼塊實例,將功能應用于metaClass的不存在的屬性上。
class Book {
String title
}
Book.metaClass.titleInUpperCase << {-> title.toUpperCase() }
def b = new Book(title:"The Stand")
assert "THE STAND" == b.titleInUpperCase()
上面的例子顯示了如何通過訪問metaClass屬性并使用<<或=運算符來分配一個Closure代碼塊來將新方法添加到類中。 Closure參數(shù)被解釋為方法參數(shù)。 無參數(shù)方法可以使用{-> ...}語法添加。
屬性
ExpandoMetaClass支持兩種機制來添加或覆蓋屬性。
首先,它支持通過向metaClass的屬性賦值來聲明一個可變屬性:
class Book {
String title
}
Book.metaClass.author = "Stephen King"
def b = new Book()
assert "Stephen King" == b.author
另一種方法是通過使用添加實例方法的標準機制來添加getter和/或setter方法。
class Book {
String title
}
Book.metaClass.getAuthor << {-> "Stephen King" }
def b = new Book()
assert "Stephen King" == b.author
在上面的源代碼示例中,屬性由閉包指定,并且是只讀屬性。 可以添加一個等效的setter方法,但是該屬性值需要被存儲以備將來使用。 這可以如下面的示例所示完成。
class Book {
String title
}
def properties = Collections.synchronizedMap([:])
Book.metaClass.setAuthor = { String value ->
properties[System.identityHashCode(delegate) + "author"] = value
}
Book.metaClass.getAuthor = {->
properties[System.identityHashCode(delegate) + "author"]
}
這不是唯一的方式。 例如,在servlet容器中,一種方式可能是將值作為請求屬性存儲在當前執(zhí)行的請求中(如在Grails中的某些情況下所做的那樣)。
構(gòu)造函數(shù)
可以通過使用特殊的構(gòu)造函數(shù)屬性來添加構(gòu)造函數(shù)。 可以使用<<或=運算符來分配Closure代碼塊。 當代碼在運行時執(zhí)行時,Closure參數(shù)將成為構(gòu)造函數(shù)參數(shù)。
class Book {
String title
}
Book.metaClass.constructor << { String title -> new Book(title:title) }
def book = new Book('Groovy in Action - 2nd Edition')
assert book.title == 'Groovy in Action - 2nd Edition'
當添加構(gòu)造函數(shù)時要小心,因為它很容易陷入堆棧溢出問題。
靜態(tài)方法
可以使用與實例方法相同的技術(shù)添加靜態(tài)方法,并在方法名稱之前添加靜態(tài)限定符。
class Book {
String title
}
Book.metaClass.static.create << { String title -> new Book(title:title) }
def b = Book.create("The Stand")
借用方法
使用ExpandoMetaClass,可以使用Groovy的方法指針語法從其他類中借用方法。
class Person {
String name
}
class MortgageLender {
def borrowMoney() {
"buy house"
}
}
def lender = new MortgageLender()
Person.metaClass.buyHouse = lender.&borrowMoney
def p = new Person()
assert "buy house" == p.buyHouse()
動態(tài)方法名稱
由于Groovy允許使用Strings作為屬性名,這反過來允許您在運行時動態(tài)創(chuàng)建方法和屬性名。 要創(chuàng)建具有動態(tài)名稱的方法,只需使用引用屬性名稱的語言特性作為字符串。
class Person {
String name = "Fred"
}
def methodName = "Bob"
Person.metaClass."changeNameTo${methodName}" = {-> delegate.name = "Bob" }
def p = new Person()
assert "Fred" == p.name
p.changeNameToBob()
assert "Bob" == p.name
相同的概念可以應用于靜態(tài)方法和屬性。
動態(tài)方法名稱的一個應用程序可以在Grails Web應用程序框架中找到。 “動態(tài)編解碼器”的概念通過使用動態(tài)方法名稱來實現(xiàn)。
HTMLCodec 類
class HTMLCodec {
static encode = { theTarget ->
HtmlUtils.htmlEscape(theTarget.toString())
}
static decode = { theTarget ->
HtmlUtils.htmlUnescape(theTarget.toString())
}
}
上面的示例顯示了編解碼器實現(xiàn)。 Grails有各種編解碼器實現(xiàn),每個在一個類中定義。 在運行時,應用程序類路徑中將有多個編解碼器類。 在應用程序啟動時,框架向某些元類添加encodeXXX和decodeXXX方法,其中XXX是編解碼器類名稱的第一部分(例如,encodeHTML)。 這種機制在下面顯示的一些Groovy偽代碼中:
def codecs = classes.findAll { it.name.endsWith('Codec') }
codecs.each { codec ->
Object.metaClass."encodeAs${codec.name-'Codec'}" = { codec.newInstance().encode(delegate) }
Object.metaClass."decodeFrom${codec.name-'Codec'}" = { codec.newInstance().decode(delegate) }
}
def html = '<html><body>hello</body></html>'
assert '<html><body>hello</body></html>' == html.encodeAsHTML()
運行時發(fā)現(xiàn)
在運行時,知道在執(zhí)行該方法時存在什么其他方法或?qū)傩酝ǔJ怯杏玫摹?ExpandoMetaClass在撰寫本文時提供了以下方法:
getMetaMethodhasMetaMethodgetMetaPropertyhasMetaProperty
你為什么不能使用反射? 因為Groovy是不同的,它有方法是“真正的”方法,同時方法只在運行時可用。 這些有時(但不總是)表示為MetaMethods。 MetaMethod告訴你在運行時可用的方法,因此你的代碼可以適應。
當覆蓋invokeMethod,getProperty和/或setProperty時,這是特別有用的。
GroovyObject 方法
ExpandoMetaClass的另一個特點是它允許重寫方法invokeMethod,getProperty和setProperty,所有這些都可以在groovy.lang.GroovyObject類中找到。
以下示例顯示如何覆蓋invokeMethod:
class Stuff {
def invokeMe() { "foo" }
}
Stuff.metaClass.invokeMethod = { String name, args ->
def metaMethod = Stuff.metaClass.getMetaMethod(name, args)
def result
if(metaMethod) result = metaMethod.invoke(delegate,args)
else {
result = "bar"
}
result
}
def stf = new Stuff()
assert "foo" == stf.invokeMe()
assert "bar" == stf.doStuff()
Closure代碼的第一步是查找給定名稱和參數(shù)的MetaMethod。 如果方法可以找到一切都很好,它被委托。 如果不是,則返回一個虛擬值。
