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] && ['a'] && [0] && [0.0] && [false] && [null]<br />
2.1.3 list迭代
迭代一个list上的元素可以使用each和eachWithIndex方法,示例代码如下:
<br />[1, 2, 3].each {<br /> println "Item: $it" // `it` is an implicit parameter corresponding to the current element<br />}<br />['a', 'b', 'c'].eachWithIndex { it, i -> // `it` is the current element, while `i` is the index<br /> println "$i: $it"<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;gt; 1 } == 2 // find 1st element matching criteria<br />assert [1, 2, 3].findAll { it &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;lt; 5 } // returns true if all elements match the predicate<br />assert ![1, 2, 3].every { it &amp;amp;lt; 3 } assert [1, 2, 3].any { it &amp;amp;gt; 2 } // returns true if any element matches the predicate<br />assert ![1, 2, 3].any { it &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;gt; str + item // reduce operation<br />} == 'counting: 123'<br />assert [1, 2, 3].inject(0) { count, item -&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;lt;/code&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 -> 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<br />
添加或删除元素
我们可以使用[]来创建一个新的空list,使用<<来追加元素在里面:
<br />def list = []<br />assert list.empty<br />list << 5<br />assert list.size() == 1<br />list << 7 << 'i' << 11<br />assert list == [5, 7, 'i', 11]<br />list << ['m', 'o']<br />assert list == [5, 7, 'i', 11, ['m', 'o']]<br />//first item in chain of << is target list<br />assert ([1, 2] << 3 << [4, 5] << 6) == [1, 2, 3, [4, 5], 6]<br />//using leftShift is equivalent to using <<<br />assert ([1, 2, 3] << 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 -> 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 }<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("foo", 5)<br />assert emptyMap.size() == 1<br />assert emptyMap.get("foo") == 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 -><br /> println "Name: $entry.key Age: $entry.value"<br />}<br />// `entry` is a map entry, `i` the index in the map<br />map.eachWithIndex { entry, i -><br /> println "$i - Name: $entry.key Age: $entry.value"<br />}<br />// Alternatively you can use key and value directly<br />map.each { key, value -><br /> println "Name: $key Age: $value"<br />}<br />// Key, value and i as the index in the map<br />map.eachWithIndex { key, value, i -><br /> println "$i - Name: $key Age: $value"<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 = "${key.toUpperCase()}"<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 -><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 -><br /> person.gender == 'M'<br />}.collect { id, person -><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 -><br /> person.age > 18<br />}<br />// `any` returns true if any entry matches the predicate<br />assert people.any { id, person -><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..<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 "Hello ${i}"<br />}<br />
当然你也可以使用更加Groovy风格的方式来实现同样的效果,通过使用each方法来迭代区间元素:
<br />(1..10).each { i -&gt;<br />println "Hello ${i}"<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</code></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]</code><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 -&gt; map.c }<br />assert f(*: ['a': 10, 'b': 20, 'c': 30], 'e': 50) == 30<br />f = { m, i, j, k -&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 />[["e": 100, "b": 20, "c": 30, "a": 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]</code><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 = "nice cheese gromit!"<br />x = text[-1]<br />assert x == "!"</code><br />def name = text[-7..-2]<br />assert name == "gromit"<br />
同样地,如果你使用一个反向区间(起点下标大于终点下标),结果也会是反的。
<br />text = "nice cheese gromit!"<br />name = text[3..1]<br />assert name == "eci"<br />
2.5 增强的集合方法
对于list, maps和ranges,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 = "Test"<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 = "Test"<br />''')<br />assert config.test != null //(1)<br />
(1)config.test还没有被实例化因此当被调用的时候将会返回一个ConfigObject
如果点号是配置文件值的一部分,可以使用单引号或双引号将其转义。
<br />def config = new ConfigSlurper().parse('''<br />app."person.age" = 42<br />''')<br />assert config.app."person.age" == 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)</code></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 = "Test"<br />}<br />''')<br />def properties = config.toProperties()<br />assert properties."app.date" instanceof String<br />assert properties."app.age" == '42'<br />assert properties."app.name" == '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 = { -&gt; 'John' }<br />expando.say = { String s -&gt; "John says: ${s}" }</code><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</code><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一样。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/117946.html