1. 程式人生 > >《Groovy官方文件》Groovy開發套件-使用集合

《Groovy官方文件》Groovy開發套件-使用集合

Groovy開發套件 第二部分

2 使用集合

Groovy提供了各種型別的原生態集合支援,包括list,  maps  和 ranges 。它們大多數都是基於Java集合型別,同時在Java集合型別中一些廢棄的方法在Groovy開發套件中也可以找到。


注:譯者也是第一次接觸Groovy,由於時間和水平有限(姑且讓譯者使用這個理由吧,對待知識本應該一絲不苟)部分專有名詞可能翻譯不準確甚至有誤(讀者閱讀的過程中最好能參考原文),懇請讀者不吝留言指出,謝謝!

2.1 Lists

2.1.1 list 基本用法

(譯者注:原文是list literals,直譯可以翻譯為list字面意思,從下文內容來看指的就是list建立和獲取元素,譯者意譯為list基本用法)
你可以使用如下方式建立一個list,注意[]是一個空list表示式。

<br />
def list = [5, 6, 7, 8]<br />
assert list.get(2) == 7<br />
assert list[2] == 7<br />
assert list instanceof java.util.List<br />
def emptyList = []<br />
assert emptyList.size() == 0<br />
emptyList.add(5)<br />
assert emptyList.size() == 1<br />

每一個list表示式都是java.util.list的一個實現。
當然,lists也可以用於構造另外一個list:

<br />
def list1 = ['a', 'b', 'c']<br />
//construct a new list, seeded with the same items as in list1<br />
def list2 = new ArrayList(list1)<br />
assert list2 == list1 // == checks that each corresponding element is the same<br />
// clone() can also be called<br />
def list3 = list1.clone()<br />
assert list3 == list1<br />

list是一個序列集合的物件(譯者注:原文是A list is an ordered collection of objects,從下文的示例程式碼來看,這裡不是有序的意思,而是相當於Java集合裡的ArrayList,因此認為翻譯為序列比有序更為恰當):

<br />
def list = [5, 6, 7, 8]<br />
assert list.size() == 4<br />
assert list.getClass() == ArrayList // the specific kind of list being used<br />
assert list[2] == 7 // indexing starts at 0<br />
assert list.getAt(2) == 7 // equivalent method to subscript operator []<br />
assert list.get(2) == 7 // alternative method<br />
list[2] = 9<br />
assert list == [5, 6, 9, 8,] // trailing comma OK<br />
list.putAt(2, 10) // equivalent method to [] when value being changed<br />
assert list == [5, 6, 10, 8]<br />
assert list.set(2, 11) == 10 // alternative method that returns old value<br />
assert list == [5, 6, 11, 8]<br />
assert ['a', 1, 'a', 'a', 2.5, 2.5f, 2.5d, 'hello', 7g, null, 9 as byte]<br />
//objects can be of different types; duplicates allowed<br />
assert [1, 2, 3, 4, 5][-1] == 5 // use negative indices to count from the end<br />
assert [1, 2, 3, 4, 5][-2] == 4<br />
assert [1, 2, 3, 4, 5].getAt(-2) == 4 // getAt() available with negative index...<br />
try {<br />
[1, 2, 3, 4, 5].get(-2) // but negative index not allowed with get()<br />
assert false<br />
} catch (e) {<br />
assert e instanceof ArrayIndexOutOfBoundsException<br />
}<br />

2.1.2 list作為一個boolean表示式

List可以當作一個boolean值:

<br />
assert ![]             // an empty list evaluates as false<br />
//all other lists, irrespective of contents, evaluate as true<br />
assert [1] &amp;&amp; ['a'] &amp;&amp; [0] &amp;&amp; [0.0] &amp;&amp; [false] &amp;&amp; [null]<br />

2.1.3 list迭代

迭代一個list上的元素可以使用each和eachWithIndex方法,示例程式碼如下:

<br />
[1, 2, 3].each {<br />
    println &quot;Item: $it&quot; // `it` is an implicit parameter corresponding to the current element<br />
}<br />
['a', 'b', 'c'].eachWithIndex { it, i -&gt; // `it` is the current element, while `i` is the index<br />
    println &quot;$i: $it&quot;<br />
}<br />

除了上面的用法,通過使用迭代,還可以將某些元素轉換為另外一種元素來建立一個新的集合。這也是非常有用的用法。這樣的操作,通常叫做對映。在Groovy裡可以使用collect方法:

<br />
assert [1, 2, 3].collect { it * 2 } == [2, 4, 6]<br />
// shortcut syntax instead of collect<br />
assert [1, 2, 3]*.multiply(2) == [1, 2, 3].collect { it.multiply(2) }<br />
def list = [0]<br />
// it is possible to give `collect` the list which collects the elements<br />
assert [1, 2, 3].collect(list) { it * 2 } == [0, 2, 4, 6]<br />
assert list == [0, 2, 4, 6]<br />

2.1.4 操作lists

過濾和搜尋
Groovy開發套件在集合操作上提供了許多方法來拓展標準集合,一些方法示例如下:

<br />
assert [1, 2, 3].find { it &amp;amp;amp;gt; 1 } == 2 // find 1st element matching criteria<br />
assert [1, 2, 3].findAll { it &amp;amp;amp;gt; 1 } == [2, 3] // find all elements matching critieria<br />
assert ['a', 'b', 'c', 'd', 'e'].findIndexOf { // find index of 1st element matching criteria<br />
it in ['c', 'e', 'g']<br />
} == 2</p>
<p>assert ['a', 'b', 'c', 'd', 'c'].indexOf('c') == 2 // index returned<br />
assert ['a', 'b', 'c', 'd', 'c'].indexOf('z') == -1 // index -1 means value not in list<br />
assert ['a', 'b', 'c', 'd', 'c'].lastIndexOf('c') == 4</p>
<p>assert [1, 2, 3].every { it &amp;amp;amp;lt; 5 } // returns true if all elements match the predicate<br />
assert ![1, 2, 3].every { it &amp;amp;amp;lt; 3 } assert [1, 2, 3].any { it &amp;amp;amp;gt; 2 } // returns true if any element matches the predicate<br />
assert ![1, 2, 3].any { it &amp;amp;amp;gt; 3 }</p>
<p>assert [1, 2, 3, 4, 5, 6].sum() == 21 // sum anything with a plus() method<br />
assert ['a', 'b', 'c', 'd', 'e'].sum {<br />
it == 'a' ? 1 : it == 'b' ? 2 : it == 'c' ? 3 : it == 'd' ? 4 : it == 'e' ? 5 : 0<br />
// custom value to use in sum<br />
} == 15<br />
assert ['a', 'b', 'c', 'd', 'e'].sum { ((char) it) - ((char) 'a') } == 10<br />
assert ['a', 'b', 'c', 'd', 'e'].sum() == 'abcde'<br />
assert [['a', 'b'], ['c', 'd']].sum() == ['a', 'b', 'c', 'd']</p>
<p>// an initial value can be provided<br />
assert [].sum(1000) == 1000<br />
assert [1, 2, 3].sum(1000) == 1006</p>
<p>assert [1, 2, 3].join('-') == '1-2-3' // String joining<br />
assert [1, 2, 3].inject('counting: ') {<br />
str, item -&amp;amp;amp;gt; str + item // reduce operation<br />
} == 'counting: 123'<br />
assert [1, 2, 3].inject(0) { count, item -&amp;amp;amp;gt;<br />
count + item<br />
} == 6<br />

下面是使用Groovy在集合中查詢最大最小值的標準程式碼:

<br />
def list = [9, 4, 2, 10, 5]<br />
assert list.max() == 10<br />
assert list.min() == 2&amp;amp;lt;/code&amp;amp;gt;</p>
<p>// we can also compare single characters, as anything comparable<br />
assert ['x', 'y', 'a', 'z'].min() == 'a'</p>
<p>// we can use a closure to specify the sorting behaviour<br />
def list2 = ['abc', 'z', 'xyzuvw', 'Hello', '321']<br />
assert list2.max { it.size() } == 'xyzuvw'<br />
assert list2.min { it.size() } == 'z'<br />

除了使用閉包,你可以使用Comparator來定義一個比較:

<br />
Comparator mc = { a, b -&gt; a == b ? 0 : (a &lt; 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 -&gt; a == b ? 0 : (Math.abs(a) &lt; Math.abs(b)) ? -1 : 1 } assert list.max(mc2) == -13 assert list.min(mc2) == -1 assert list.max { a, b -&gt; a.equals(b) ? 0 : Math.abs(a) &lt; Math.abs(b) ? -1 : 1 } == -13 assert list.min { a, b -&gt; a.equals(b) ? 0 : Math.abs(a) &lt; Math.abs(b) ? -1 : 1 } == -1<br />

新增或刪除元素
我們可以使用[]來建立一個新的空list,使用<<來追加元素在裡面:

<br />
def list = []<br />
assert list.empty<br />
list &lt;&lt; 5<br />
assert list.size() == 1<br />
list &lt;&lt; 7 &lt;&lt; 'i' &lt;&lt; 11<br />
assert list == [5, 7, 'i', 11]<br />
list &lt;&lt; ['m', 'o']<br />
assert list == [5, 7, 'i', 11, ['m', 'o']]<br />
//first item in chain of &lt;&lt; is target list<br />
assert ([1, 2] &lt;&lt; 3 &lt;&lt; [4, 5] &lt;&lt; 6) == [1, 2, 3, [4, 5], 6]<br />
//using leftShift is equivalent to using &lt;&lt;<br />
assert ([1, 2, 3] &lt;&lt; 4) == ([1, 2, 3].leftShift(4))<br />

我們也可以使用下面的方法來新增元素:

<br />
assert [1, 2] + 3 + [4, 5] + 6 == [1, 2, 3, 4, 5, 6]<br />
// equivalent to calling the `plus` method<br />
assert [1, 2].plus(3).plus([4, 5]).plus(6) == [1, 2, 3, 4, 5, 6]<br />
def a = [1, 2, 3]<br />
a += 4 // creates a new list and assigns it to `a`<br />
a += [5, 6]<br />
assert a == [1, 2, 3, 4, 5, 6]<br />
assert [1, *[222, 333], 456] == [1, 222, 333, 456]<br />
assert [*[1, 2, 3]] == [1, 2, 3]<br />
assert [1, [2, 3, [4, 5], 6], 7, [8, 9]].flatten() == [1, 2, 3, 4, 5, 6, 7, 8, 9]<br />
def list = [1, 2]<br />
list.add(3)<br />
list.addAll([5, 4])<br />
assert list == [1, 2, 3, 5, 4]<br />
list = [1, 2]<br />
list.add(1, 3) // add 3 just before index 1<br />
assert list == [1, 3, 2]<br />
list.addAll(2, [5, 4]) //add [5,4] just before index 2<br />
assert list == [1, 3, 5, 4, 2]<br />
list = ['a', 'b', 'z', 'e', 'u', 'v', 'g']<br />
list[8] = 'x' // the [] operator is growing the list as needed<br />
// nulls inserted if required<br />
assert list == ['a', 'b', 'z', 'e', 'u', 'v', 'g', null, 'x']<br />

特別重要的是+操作不能改變一個集合。和<<相比,它會建立一個新的list,這可能通常不是你想要的結果,同時會有效能問題。(譯者注:這裡和Java中String型別的+操作類似)
Groovy開發套件提供了下面的方法來輕鬆實現從集合中刪除元素:

<br />
assert ['a','b','c','b','b'] - 'c' == ['a','b','b','b']<br />
assert ['a','b','c','b','b'] - 'b' == ['a','c']<br />
assert ['a','b','c','b','b'] - ['b','c'] == ['a']<br />
def list = [1,2,3,4,3,2,1]<br />
list -= 3 // creates a new list by removing `3` from the original one<br />
assert list == [1,2,4,2,1]<br />
assert ( list -= [2,4] ) == [1,1]<br />

也可以使用下標操作來刪除元素,這樣會使原集合發生改變:

<br />
def list = [1,2,3,4,5,6,2,2,1]<br />
assert list.remove(2) == 3 // remove the third element, and return it<br />
assert list == [1,2,4,5,6,2,2,1]<br />

有時候你僅僅想刪除第一次出現的元素,而不是刪除全部匹配的元素,你可以這樣是使用remove方法:

<br />
def list= ['a','b','c','b','b']<br />
assert list.remove('c') // remove 'c', and return true because element removed<br />
assert list.remove('b') // remove first 'b', and return true because element removed<br />
assert ! list.remove('z') // return false because no elements removed<br />
assert list == ['a','b','b']<br />

清空一個list可以使用clear方法:

<br />
def list= ['a',2,'c',4]<br />
list.clear()<br />
assert list == []<br />

Set操作
Groovy開發套件同樣也提供了許多方法來方便進行Sets操作

<br />
assert 'a' in ['a','b','c'] // returns true if an element belongs to the list<br />
assert ['a','b','c'].contains('a') // equivalent to the `contains` method in Java<br />
assert [1,3,4].containsAll([1,4]) // `containsAll` will check that all elements are found<br />
assert [1,2,3,3,3,3,4,5].count(3) == 4 // count the number of elements which have some value<br />
assert [1,2,3,3,3,3,4,5].count {<br />
it%2==0 // count the number of elements which match the predicate<br />
} == 2<br />
assert [1,2,4,6,8,10,12].intersect([1,3,6,9,12]) == [1,6,12]<br />
assert [1,2,3].disjoint( [4,6,9] )<br />
assert ![1,2,3].disjoint( [2,4,6] )<br />

排序
使用Collections的排序,Groovy提供了許多引數來排序一個lists,從使用閉包到使用比較器,示例程式碼如下:

<br />
assert [6, 3, 9, 2, 7, 1, 5].sort() == [1, 2, 3, 5, 6, 7, 9]<br />
def list = ['abc', 'z', 'xyzuvw', 'Hello', '321']<br />
assert list.sort {<br />
    it.size()<br />
} == ['z', 'abc', '321', 'Hello', 'xyzuvw']<br />
def list2 = [7, 4, -6, -1, 11, 2, 3, -9, 5, -13]<br />
assert list2.sort { a, b -&gt; a == b ? 0 : Math.abs(a) &lt; Math.abs(b) ? -1 : 1 } ==         [-1, 2, 3, 4, 5, -6, 7, -9, 11, -13] Comparator mc = { a, b -&gt; a == b ? 0 : Math.abs(a) &lt; Math.abs(b) ? -1 : 1 }<br />
// JDK 8+ only<br />
// list2.sort(mc)<br />
// assert list2 == [-1, 2, 3, 4, 5, -6, 7, -9, 11, -13]<br />
def list3 = [6, -3, 9, 2, -7, 1, 5]<br />
Collections.sort(list3)<br />
assert list3 == [-7, -3, 1, 2, 5, 6, 9]<br />
Collections.sort(list3, mc)<br />
assert list3 == [1, 2, -3, 5, 6, -7, 9]<br />

複製元素
Groovy開發套件過載了一些操作來對集合進行復制操作:

<br />
assert [1, 2, 3] * 3 == [1, 2, 3, 1, 2, 3, 1, 2, 3]<br />
assert [1, 2, 3].multiply(2) == [1, 2, 3, 1, 2, 3]<br />
assert Collections.nCopies(3, 'b') == ['b', 'b', 'b']<br />
// nCopies from the JDK has different semantics than multiply for lists<br />
assert Collections.nCopies(2, [1, 2]) == [[1, 2], [1, 2]] //not [1,2,1,2]<br />

2.2 Maps

2.2.1 Map基本操作

在Groovy裡,maps(通常和arrays聯絡在一起)可以使用 [:] 來建立:

<br />
def map = [name: 'Gromit', likes: 'cheese', id: 1234]<br />
assert map.get('name') == 'Gromit'<br />
assert map.get('id') == 1234<br />
assert map['name'] == 'Gromit'<br />
assert map['id'] == 1234<br />
assert map instanceof java.util.Map<br />
def emptyMap = [:]<br />
assert emptyMap.size() == 0<br />
emptyMap.put(&quot;foo&quot;, 5)<br />
assert emptyMap.size() == 1<br />
assert emptyMap.get(&quot;foo&quot;) == 5<br />

Map的key預設是字串型別的,[a:1]和[‘a’:1]是等效的。如果你的變數名字恰好也是a 那就會導致混亂了,可能你是想將 a 的值作為你map的key。如果是這種情況,你必須使用括號來轉義,類似下面的例子:

<br />
def a = 'Bob'<br />
def ages = [a: 43]<br />
assert ages['Bob'] == null // `Bob` is not found<br />
assert ages['a'] == 43 // because `a` is a literal!<br />
ages = [(a): 43] // now we escape `a` by using parenthesis<br />
assert ages['Bob'] == 43 // and the value is found!<br />

除了map的基本操作,要得到一個map的新的拷貝,可以clone它:

<br />
def map = [<br />
simple : 123,<br />
complex: [a: 1, b: 2]<br />
]<br />
def map2 = map.clone()<br />
assert map2.get('simple') == map.get('simple')<br />
assert map2.get('complex') == map.get('complex')<br />
map2.get('complex').put('c', 3)<br />
assert map.get('complex').get('c') == 3<br />

上面的例子得到的就是原始map的一個投影。

2.2.2 Map屬性標記

Maps可以像你操作Bean那樣通過使用屬性標記來get/set Map內部的元素。只要key是Groovy識別的字串:

<br />
def map = [name: 'Gromit', likes: 'cheese', id: 1234]<br />
assert map.name == 'Gromit' // can be used instead of map.get('Gromit')<br />
assert map.id == 1234<br />
def emptyMap = [:]<br />
assert emptyMap.size() == 0<br />
emptyMap.foo = 5<br />
assert emptyMap.size() == 1<br />
assert emptyMap.foo == 5<br />

注意:預設情況下map.foo將總是在map中搜索key為foo的元素。這意味著如果一個map鐘不含有class的可以,foo.class將會返回null。如果你只是想知道類型別,你必須使用getClass() :

<br />
def map = [name: 'Gromit', likes: 'cheese', id: 1234]<br />
assert map.class == null<br />
assert map.get('class') == null<br />
assert map.getClass() == LinkedHashMap // this is probably what you want<br />
map = [1 : 'a',<br />
(true) : 'p',<br />
(false): 'q',<br />
(null) : 'x',<br />
'null' : 'z']<br />
assert map.containsKey(1) // 1 is not an identifier so used as is<br />
assert map.true == null<br />
assert map.false == null<br />
assert map.get(true) == 'p'<br />
assert map.get(false) == 'q'<br />
assert map.null == 'z'<br />
assert map.get(null) == 'x'<br />

2.2.3 Maps的迭代

通常在Groovy開發套件裡,迭代map是用each和eachWithIndex方法。maps的建立也是序列的,也就是說當你迭代一個map的時候,可以保證迭代的順序就是新增元素的順序:

<br />
def map = [<br />
        Bob  : 42,<br />
        Alice: 54,<br />
        Max  : 33<br />
]<br />
// `entry` is a map entry<br />
map.each { entry -&gt;<br />
    println &quot;Name: $entry.key Age: $entry.value&quot;<br />
}<br />
// `entry` is a map entry, `i` the index in the map<br />
map.eachWithIndex { entry, i -&gt;<br />
    println &quot;$i - Name: $entry.key Age: $entry.value&quot;<br />
}<br />
// Alternatively you can use key and value directly<br />
map.each { key, value -&gt;<br />
    println &quot;Name: $key Age: $value&quot;<br />
}<br />
// Key, value and i as the index in the map<br />
map.eachWithIndex { key, value, i -&gt;<br />
    println &quot;$i - Name: $key Age: $value&quot;<br />
}<br />

2.2.4 操作maps

新增或刪除元素
新增一個元素到map可以使用put,批量操作可以使用putAll:

<br />
def defaults = [1: 'a', 2: 'b', 3: 'c', 4: 'd']<br />
def overrides = [2: 'z', 5: 'x', 13: 'x']<br />
def result = new LinkedHashMap(defaults)<br />
result.put(15, 't')<br />
result[17] = 'u'<br />
result.putAll(overrides)<br />
assert result == [1: 'a', 2: 'z', 3: 'c', 4: 'd', 5: 'x', 13: 'x', 15: 't', 17: 'u']<br />

刪除一個map鍾全部元素可以使用clear方法:

<br />
def m = [1:'a', 2:'b']<br />
assert m.get(1) == 'a'<br />
m.clear()<br />
assert m == [:]<br />

使用map基本操作生成的map使用了物件的equals和hashcode方法。這個意味著你不應該使用一個hash code經常改變的物件或者其值不可逆操作的物件放入map。
你使用一個GString來作為map的key也是沒有意義的。因為GString的hashcode和String的hashcode並不相等。

<br />
def key = 'some key'<br />
def map = [:]<br />
def gstringKey = &quot;${key.toUpperCase()}&quot;<br />
map.put(gstringKey,'value')<br />
assert map.get('SOME KEY') == null<br />

keys,values和entries
我們可以在一個視圖裡檢視keys,values和entries:

<br />
def map = [1:'a', 2:'b', 3:'c']<br />
def entries = map.entrySet()<br />
entries.each { entry -&gt;<br />
  assert entry.key in [1,2,3]<br />
  assert entry.value in ['a','b','c']<br />
}<br />
def keys = map.keySet()<br />
assert keys == [1,2,3] as Set<br />

通過檢視(也就是map的key,entry和value)返回的值來操作values是強烈不推薦的方式。因為maps操作已經有很多直接操作的方法。特別地,Groovy依賴於JDK的一些方法並不保證能夠安全地操作集合,比如keySet,entrySet或values
過濾和搜尋
Groovy開發套件包含過濾,搜尋集合方法,這個和list是類似的:

<br />
def people = [<br />
    1: [name:'Bob', age: 32, gender: 'M'],<br />
    2: [name:'Johnny', age: 36, gender: 'M'],<br />
    3: [name:'Claire', age: 21, gender: 'F'],<br />
    4: [name:'Amy', age: 54, gender:'F']<br />
]<br />
def bob = people.find { it.value.name == 'Bob' } // find a single entry<br />
def females = people.findAll { it.value.gender == 'F' }<br />
// both return entries, but you can use collect to retrieve the ages for example<br />
def ageOfBob = bob.value.age<br />
def agesOfFemales = females.collect {<br />
    it.value.age<br />
}<br />
assert ageOfBob == 32<br />
assert agesOfFemales == [21,54]<br />
// but you could also use a key/pair value as the parameters of the closures<br />
def agesOfMales = people.findAll { id, person -&gt;<br />
    person.gender == 'M'<br />
}.collect { id, person -&gt;<br />
    person.age<br />
}<br />
assert agesOfMales == [32, 36]<br />
// `every` returns true if all entries match the predicate<br />
assert people.every { id, person -&gt;<br />
    person.age &gt; 18<br />
}<br />
// `any` returns true if any entry matches the predicate<br />
assert people.any { id, person -&gt;<br />
    person.age == 54<br />
}<br />

分組
我們可以將一個list按某些維度分組到一個map中:

<br />
assert ['a', 7, 'b', [2, 3]].groupBy {<br />
it.class<br />
} == [(String) : ['a', 'b'],<br />
(Integer) : [7],<br />
(ArrayList): [[2, 3]]<br />
]<br />
assert [<br />
[name: 'Clark', city: 'London'], [name: 'Sharma', city: 'London'],<br />
[name: 'Maradona', city: 'LA'], [name: 'Zhang', city: 'HK'],<br />
[name: 'Ali', city: 'HK'], [name: 'Liu', city: 'HK'],<br />
].groupBy { it.city } == [<br />
London: [[name: 'Clark', city: 'London'],<br />
[name: 'Sharma', city: 'London']],<br />
LA : [[name: 'Maradona', city: 'LA']],<br />
HK : [[name: 'Zhang', city: 'HK'],<br />
[name: 'Ali', city: 'HK'],<br />
[name: 'Liu', city: 'HK']],<br />
]<br />

2.3 Ranges(區間)

Ranges允許你建立一個序列值得list,它可以當成list用,因為Range繼承了java.util.List
Ranges定義使用 .. 代表閉區間(包括起點和終點)
Ranges定義使用 ..< 代表一個半開半閉,只包含第一個值不包含最後一個值。

<br />
// an inclusive range<br />
def range = 5..8<br />
assert range.size() == 4<br />
assert range.get(2) == 7<br />
assert range[2] == 7<br />
assert range instanceof java.util.List<br />
assert range.contains(5)<br />
assert range.contains(8)<br />
// lets use a half-open range<br />
range = 5..&lt;8<br />
assert range.size() == 3<br />
assert range.get(2) == 7<br />
assert range[2] == 7<br />
assert range instanceof java.util.List<br />
assert range.contains(5)<br />
assert !range.contains(8)<br />
//get the end points of the range without using indexes<br />
range = 1..10<br />
assert range.from == 1<br />
assert range.to == 10<br />

可以看到建立一個int區間是非常高效的,可以建立一個非常輕量級的包含起點值和終點值得物件。
區間也可以用作實現了Java.lang.Comparable介面作為比較器的任何Java物件。可以使用next()和previous()來返回netx/previous元素。舉個例子,你可以建立一個String元素型別的區間:

<br />
// an inclusive range<br />
def range = 'a'..'d'<br />
assert range.size() == 4<br />
assert range.get(2) == 'c'<br />
assert range[2] == 'c'<br />
assert range instanceof java.util.List<br />
assert range.contains('a')<br />
assert range.contains('d')<br />
assert !range.contains('e')<br />

你可以使用for迴圈來迭代區間元素:

<br />
for (i in 1..10) {<br />
println &quot;Hello ${i}&quot;<br />
}<br />

當然你也可以使用更加Groovy風格的方式來實現同樣的效果,通過使用each方法來迭代區間元素:

<br />
(1..10).each { i -&amp;gt;<br />
println &quot;Hello ${i}&quot;<br />
}<br />

區間同樣可以用在switch語句中:

<br />
switch (years) {<br />
case 1..10: interestRate = 0.076; break;<br />
case 11..25: interestRate = 0.052; break;<br />
default: interestRate = 0.037;<br />
}<br />

2.4 Collections的語法增強

2.4.1 GPath支援

幸虧屬性標記對Lists和Maps都支援,Groovy 提供了非常實用的方法來使巢狀集合處理變得非常簡潔,示例程式碼如下:

<br />
def listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22]]<br />
assert listOfMaps.a == [11, 21] //GPath notation<br />
assert listOfMaps*.a == [11, 21] //spread dot notation&lt;/code&gt;</p>
<p>listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22], null]<br />
assert listOfMaps*.a == [11, 21, null] // caters for null values<br />
assert listOfMaps*.a == listOfMaps.collect { it?.a } //equivalent notation<br />
// But this will only collect non-null values<br />
assert listOfMaps.a == [11,21]<br />

2.4.2 Spread操作

Spread操作可以認為是將一個集合內聯到另外一個集合。這樣就可以避免使用putAll方法從而將實現變得只要一行程式碼:

<br />
assert [ 'z': 900,<br />
*: ['a': 100, 'b': 200], 'a': 300] == ['a': 300, 'b': 200, 'z': 900]<br />
//spread map notation in map definition<br />
assert [*: [3: 3, *: [5: 5]], 7: 7] == [3: 3, 5: 5, 7: 7]&lt;/code&gt;<br />
def f = { [1: 'u', 2: 'v', 3: 'w'] }<br />
assert [*: f(), 10: 'zz'] == [1: 'u', 10: 'zz', 2: 'v', 3: 'w']<br />
//spread map notation in function arguments<br />
f = { map -&amp;gt; map.c }<br />
assert f(*: ['a': 10, 'b': 20, 'c': 30], 'e': 50) == 30<br />
f = { m, i, j, k -&amp;gt; [m, i, j, k] }<br />
//using spread map notation with mixed unnamed and named arguments<br />
assert f('e': 100, *[4, 5], *: ['a': 10, 'b': 20, 'c': 30], 6) ==<br />
[[&quot;e&quot;: 100, &quot;b&quot;: 20, &quot;c&quot;: 30, &quot;a&quot;: 10], 4, 5, 6]<br />

2.4.3 星號操作 *

星號操作是允許你在一個集合的全部元素中呼叫某個方法或屬性的簡潔操作:

<br />
assert [1, 3, 5] == ['a', 'few', 'words']*.size()<br />
class Person {<br />
String name<br />
int age<br />
}<br />
def persons = [new Person(name:'Hugo', age:17), new Person(name:'Sandra',age:19)]<br />
assert [17, 19] == persons*.age<br />

2.4.4 使用下標操作來分片

你可以使用下標來索引lists,arrays,maps。字串型別被當成一種特殊的集合型別:

<br />
def text = 'nice cheese gromit!'<br />
def x = text[2]&lt;/code&gt;<br />
assert x == 'c'<br />
assert x.class == String<br />
def sub = text[5..10]<br />
assert sub == 'cheese'<br />
def list = [10, 11, 12, 13]<br />
def answer = list[2,3]<br />
assert answer == [12,13]<br />

注意你可以使用區間來提取集合:

<br />
list = 100..200<br />
sub = list[1, 3, 20..25, 33]<br />
assert sub == [101, 103, 120, 121, 122, 123, 124, 125, 133]<br />

下標操作也可以用於更新已有集合(對於那些不可變集合)

<br />
list = ['a','x','x','d']<br />
list[1..2] = ['b','c']<br />
assert list == ['a','b','c','d']<br />

值得注意的是負數也是被允許的,表示從集合後面提取元素:你可以使用負數來表示從尾開始操作list,array,String等:

<br />
text = &quot;nice cheese gromit!&quot;<br />
x = text[-1]<br />
assert x == &quot;!&quot;&lt;/code&gt;<br />
def name = text[-7..-2]<br />
assert name == &quot;gromit&quot;<br />

同樣地,如果你使用一個反向區間(起點下標大於終點下標),結果也會是反的。

<br />
text = &quot;nice cheese gromit!&quot;<br />
name = text[3..1]<br />
assert name == &quot;eci&quot;<br />

2.5 增強的集合方法

對於list, mapsranges,Groovy提供了許多額外的方法來過濾,集合分組,技術等等,那些方法使集合操作更加簡單,迭代操作更加容易。

特別地,我們希望你能特別地讀一下Groovy開發套件的API文件:

3 有用的工具類

3.1 ConfigSlurper

ConfigSlurper是用來讀配置檔案的工具類,通常是Grooy指令碼格式的配置檔案。類似於Java裡的*.properties檔案。ConfigSlurper允許點操作符,除此之外,還允許閉包操作配置值和一些物件型別

<br />
def config = new ConfigSlurper().parse('''<br />
app.date = new Date() (1)<br />
app.age = 42<br />
app { (2)<br />
name = &quot;Test${42}&quot;<br />
}<br />
''')<br />
assert config.app.date instanceof Date<br />
assert config.app.age == 42<br />
assert config.app.name == 'Test42'<br />

(1)使用點操作符
(2)使用閉包來代替點操作符
從上面的例子可以看到,parse方法可以用來返回groovy.util.ConfigObject例項,ConfigObject是一種特別的java.util.Map實現。既可以用於返回配置值,也可以返回一個不為null的新的ConfigObject例項物件

<br />
def config = new ConfigSlurper().parse('''<br />
app.date = new Date()<br />
app.age = 42<br />
app.name = &quot;Test${42}&quot;<br />
''')<br />
assert config.test != null //(1)<br />

(1)config.test還沒有被例項化因此當被呼叫的時候將會返回一個ConfigObject
如果點號是配置檔案值的一部分,可以使用單引號或雙引號將其轉義。

<br />
def config = new ConfigSlurper().parse('''<br />
app.&quot;person.age&quot; = 42<br />
''')<br />
assert config.app.&quot;person.age&quot; == 42<br />

除此之外,ConfigSlurper也支援environments . enviroments方法可以被用來處理閉包例項,它自身也有可能由好幾個部分組成。假如說我們想建立一個特定的配置值來給開發環境用,當我們建立一個ConfigSlurper例項的時候我們可以使用ConfigSlurper(String)建構函式來實現特定環境的配置.

<br />
def config = new ConfigSlurper('development').parse('''<br />
environments {<br />
development {<br />
app.port = 8080<br />
}<br />
test {<br />
app.port = 8082<br />
}<br />
production {<br />
app.port = 80<br />
}<br />
}<br />
''')<br />
assert config.app.port == 8080<br />

ConfigSlurper環境變數沒有嚴格遵循任何環境變數名字。取決於特定的ConfigSlurper客戶端程式碼。
enviroments方法是一個那隻方法,但是registerConditionalBlock方法可以用於註冊其他方法名字,並且可以是enviroments名字。

<br />
def slurper = new ConfigSlurper()<br />
slurper.registerConditionalBlock('myProject', 'developers') (1)&lt;/code&gt;</p>
<p>def config = slurper.parse('''<br />
sendMail = true</p>
<p>myProject {<br />
developers {<br />
sendMail = false<br />
}<br />
}<br />
''')</p>
<p>assert !config.sendMail<br />

(1)一旦一個新的塊註冊了,ConfigSlurper可以編碼它
因為Java整合原因,toProperties方法可以用於將ConfigObject物件轉換到java.util.Properties物件。然後可以將其存在*.properties文字檔案中。注意,在新建一個Properties例項的時候配置值將會轉換為String型別例項。

<br />
def config = new ConfigSlurper().parse('''<br />
app.date = new Date()<br />
app.age = 42<br />
app {<br />
name = &quot;Test${42}&quot;<br />
}<br />
''')<br />
def properties = config.toProperties()<br />
assert properties.&quot;app.date&quot; instanceof String<br />
assert properties.&quot;app.age&quot; == '42'<br />
assert properties.&quot;app.name&quot; == 'Test42'<br />

3.2 Expando

Expando類可以用於動態建立可拓展物件。儘管它的類名沒有采用ExpandoMetaClass。每一個Expando物件代表一個獨立的動態的例項,可以在執行時被屬性或方法所拓展

<br />
def expando = new Expando()<br />
expando.name = 'John'<br />
assert expando.name == 'John'<br />

一個特殊的例子是當一個動態屬性註冊到一個閉包程式碼塊。一個註冊就可以被方法所動態呼叫:

<br />
def expando = new Expando()<br />
expando.toString = { -&amp;gt; 'John' }<br />
expando.say = { String s -&amp;gt; &quot;John says: ${s}&quot; }&lt;/code&gt;<br />
assert expando as String == 'John'<br />
assert expando.say('Hi') == 'John says: Hi'<br />

3.3 可觀察的list,map和set

Groovy提供了可觀察的lists,maps和sets。每一個都是一個java.beans.propertyChangeEvnent事件的觸發器。當元素被新增,刪除,修改就會被觸發。注意PropertyChangeEvent不僅僅當某些事件發生才出發,同時可以儲存新舊值。
可能會有型別改變,可觀察的集合可能需要更加特別的PropertyChangeEvnet型別。比如當新增一個元素到可觀察list觸發一個ObservableList.ElementAddedEvent事件。

<br />
def event (1)<br />
def listener = {<br />
if (it instanceof ObservableList.ElementEvent) { (2)<br />
event = it<br />
}<br />
} as PropertyChangeListener<br />
def observable = [1, 2, 3] as ObservableList (3)<br />
observable.addPropertyChangeListener(listener) (4)<br />
observable.add 42 (5)<br />
assert event instanceof ObservableList.ElementAddedEvent<br />
def elementAddedEvent = event as ObservableList.ElementAddedEvent<br />
assert elementAddedEvent.changeType == ObservableList.ChangeType.ADDED<br />
assert elementAddedEvent.index == 3<br />
assert elementAddedEvent.oldValue == null<br />
assert elementAddedEvent.newValue == 42<br />

(1)宣告一個PropertyChangeEventListener可以捕捉觸發事件
(2)ObservableList.ElementEvent和它的相關型別是相對與這個監聽器
(3)註冊一個監聽器
(4)從給定的list建立一個ObservableList
(5)ObservableList.ElementAddedEvent事件的觸發器
注意,新增一個元素將會觸發兩個事件,第一個是ObservableList.ElementAddedEvent 第二個是 PropertyChangeEvent,用來通知監聽器這次修改屬性的size

ObservableList.ElementClearedEvent 事件型別是另外一個有趣的事件。無論什麼時候多個元素被刪除,比如當我們呼叫clear()方法的時候,它將會儲存被刪除的元素

<br />
def event<br />
def listener = {<br />
if (it instanceof ObservableList.ElementEvent) {<br />
event = it<br />
}<br />
} as PropertyChangeListener&lt;/code&gt;<br />
def observable = [1, 2, 3] as ObservableList<br />
observable.addPropertyChangeListener(listener)<br />
observable.clear()<br />
assert event instanceof ObservableList.ElementClearedEvent<br />
def elementClearedEvent = event as ObservableList.ElementClearedEvent<br />
assert elementClearedEvent.values == [1, 2, 3]<br />
assert observable.size() == 0<br />

為了瞭解整個事件型別,建議讀者閱讀JavaDoc文件或可觀察集合的原始碼。
這個章節裡,ObservableMap和ObservableSet是同一個概念,和我們見過的ObservableList一樣。