Groovy 开发工具包
1. 处理 IO 操作Groovy 提供了很多辅助方法,尽管可以使用 Java 来解决,但 Groovy 还是更多方便的方法来处理文件、流、阅读器,等等。 你特别需要注意添加到这些类中的方法:
下面,使用上面提供的辅助函数来介绍一些简单而又符合 Groovy 语言习惯的构建范例,更多的有关方法可参看 GDK API。 1.1 读取文件作为第一个范例,我们来看一看如何将文本文件的所有行都打印出来: new File(baseDir,'haiku.txt').eachLine { line -> println line }
new File(baseDir,'haiku.txt').eachLine { line,nb -> println "Line $nb: $line" } 假如因为某种原因, 比如在一些情况下,你更喜欢用 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') } } } 有时,你可能会需要将某个文本文件的行放入列表中,可以这样写:
或者,你甚至还可以利用
你还记得有多少次必须将文件内容用
处理 I/O 并不限于操作文件。实际上,很多操作都依赖于输入输出流,这就是 Groovy 为何要重新添加大量方法的原因。关于这些,可以查看详细的文档。 再次举例,我们可以非常轻松地从一个 def is = new File(baseDir,'haiku.txt').newInputStream() // 省略的逻辑语句...... is.close() 但是,从上面的代码我们也可以看到,你还需要手动关闭这个输入流的。在 Groovy 中,一般最好使用 new File(baseDir,'haiku.txt').withInputStream { stream -> // 省略的逻辑语句...... } 1.2 写入文件在某些情况下,你可能只需要写入文件,而不需要读取。这时,使用 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!''' 当然我们并不一定总是处理文本内容,使用
无疑,你也可以直接处理输出流,比如下面这个例子就通过创建输出流,再将其写入一个文件: def os = new File(baseDir,'data.bin').newOutputStream() // 省略的逻辑语句...... os.close() 但我们再次看到,需要手动编写关闭输出流的语句,与输入流的情况相同,一般在 Groovy 中最好使用 new File(baseDir,'data.bin').withOutputStream { stream -> // 省略的逻辑语句...... } 1.3 遍历文件树在编写上下文脚本时,常见的一个任务就是遍历文件树查找某些特定文件,然后对它们进行一定的处理。Groovy 为此提供了多种方法。例如,你可以对某个目录的所有文件执行一些操作: dir.eachFile { file -> // 1?? println file.name } dir.eachFileMatch(~/.*.txt/) { file -> // 2?? println file.name } 1?? 对目录中已找到的所有文件执行闭包代码。 另外,我们往往必须处理较深层次的文件,这种情况下可以使用 dir.eachFileRecurse { file -> // 1?? println file.name } dir.eachFileRecurse(FileType.FILES) { file -> // 2?? println file.name } 1?? 对目录中已找到的所有文件或目录递归地执行闭包代码。 要想使用更复杂的遍历技术,你可以使用 dir.traverse { file -> if (file.directory && file.name=='bin') { FileVisitResult.TERMINATE // 1?? } else { println file.name FileVisitResult.CONTINUE // 2?? } } 1?? 如果当前文件是目录或者它的名称为 1.4 数据和对象在 Java 中,利用 boolean b = true String message = 'Hello from Groovy' // 将文件序列化到文件中 file.withDataOutputStream { out -> out.writeBoolean(b) out.writeUTF(message) } // ... // 然后再重新把它读取出来 file.withDataInputStream { input -> assert input.readBoolean() == b assert input.readUTF() == message } 同样,如果想要序列化的数据实现了 Person p = new Person(name:'Bob',age:76) // 将文件序列化到文件中 file.withObjectOutputStream { out -> out.writeObject(p) } // ... // 然后再重新把它读取出来 file.withObjectInputStream { input -> def p2 = input.readObject() assert p2.name == p.name assert p2.age == p.age } 1.5 执行外部进程前面几节介绍了在 Groovy 中处理文件、Reader 或流的便利性,但在系统管理或开发运维领域中,我们经常还需要与外部进程进行通信。 Groovy 执行命令行进程的方式非常简单,只需把命令行写成字符串的形式,然后调用 def process = "ls -l".execute() // 1?? println "Found text ${process.text}" // 2?? 1?? 在外部进程中执行
比如,下例中的命令与上例相同,但这次我们每次只处理一行结果流: def process = "ls -l".execute() // 1?? process.in.eachLine { line -> // 2?? println line // 3?? } 1?? 在外部进程中执行 上例中,值得我们注意的是, 另外一点要记住的是,很多命令都是 shell 内置的,需要特殊处理。假设在 Windows 系统机器上想用以下方式获取文件列表: def process = "dir".execute() println "${process.text}" 你会得到这样一个 这是因为 def process = "cmd /c dir".execute() println "${process.text}" 另外,由于该功能当前其实秘密使用了 > 由于一些原生平台上所提供的输出和输入流的缓冲区十分有限,所以如果未能及时地写入子进程的输入流或读取子进程的输出流,可能会导致子进程的阻塞甚至死锁。正是由于这个特点,Groovy 提供了一些额外的帮助方法来使进程流的控制更为容易。 下面这个例子说明了如何获取进程中的所有输出(包括错误流输出): def p = "rm -f foo.tmp".execute([],tmpDir) p.consumeProcessOutput() p.waitFor() 目前存在很多 另外, 下面介绍一些有关它的用例: 管道实例: 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" 2. 使用集合Groovy 为多种集合提供了原生支持,这些类型的集合包括:list、map(映射) 或 range(范围)。它们大多基于 java 集合类型,并且附带有 GDK中的一些额外方法。 2.1 List(列表)2.1.1 列表字面量你可以按照如下方式创建 lists,注意 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 每一个 list 表达式都创建了一个 当然也可以把 list 作为构建其他 list 的来源: def list1 = ['a','b','c'] //基于 list1 中的项构建一个新的列表 def list2 = new ArrayList<String>(list1) assert list2 == list1 // == 用来检查两个列表中的每个相应元素是否相同 // 也可以调用 clone() def list3 = list1.clone() assert list3 == list1 列表其实是一个有序的对象集合: def list = [5,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,9,8,] // 允许末尾出现逗号 list.putAt(2,10) // 当数值发生变动时,[] 的等效方法 assert list == [5,10,8] assert list.set(2,11) == 10 // 返回旧值的另一种方法 assert list == [5,11,8] assert ['a',1,'a',2.5,2.5f,2.5d,'hello',7g,null,9 as byte] // 对象的类型可以不同,对象也允许重复。 assert [1,2,3,4,5][-1] == 5 // 使用负数代表从末尾开始计算索引 assert [1,5][-2] == 4 assert [1,5].getAt(-2) == 4 // getAt() 允许出现负数索引...... try { [1,5].get(-2) // 但负数索引不允许用于 get() 方法 assert false } catch (e) { assert e instanceof ArrayIndexOutOfBoundsException } 2.1.2 作为布尔表达式的列表可以将列表用作 assert ![] // 空列表的结果是 false //所有其他列表,不管其中包含的内容,其布尔值都是 true assert [1] && ['a'] && [0] && [0.0] && [false] && [null] 2.1.3 列表中的迭代通常,列表中的迭代需要调用 [1,3].each { println "Item: $it" // `it` 是个很隐含的参数,对应着当前元素 } ['a','c'].eachWithIndex { it,i -> // `it` 是当前元素,而 `i` 则是索引 println "$i: $it" } 除了迭代之外,往往还用到将列表中的每个元素转换成其他内容,从而创建一个新的列表。这种操作常被称为映射(mapping),在 Groovy 中利用 assert [1,3].collect { it * 2 } == [2,6] // 代替 `collect` 的快捷格式 assert [1,3]*.multiply(2) == [1,3].collect { it.multiply(2) } def list = [0] // 有可能要给 `collect` 提供收集元素的列表 assert [1,3].collect(list) { it * 2 } == [0,6] assert list == [0,6] 2.1.4 操纵列表过滤与搜索Groovy Development Kit中包含着与集合相关的大量方法,这些实用性的方法大大增强了集合的标准操作。其中一些方法如下所示: assert [1,3].find { it > 1 } == 2 // 查找符合规则的第一个元素 assert [1,3].findAll { it > 1 } == [2,3] // 查找符合规则的所有元素 assert ['a','c','d','e'].findIndexOf { // 查找符合规则的第一个元素的索引 it in ['c','e','g'] } == 2 assert ['a','c'].indexOf('c') == 2 // 返回索引 assert ['a','c'].indexOf('z') == -1 // 索引 -1 意指该值并不在列表中 assert ['a','c'].lastIndexOf('c') == 4 assert [1,3].every { it < 5 } // 如果所有元素都符合断言,则返回 true assert ![1,3].every { it < 3 } assert [1,3].any { it > 2 } // 只要有元素符合断言,就返回 true assert ![1,3].any { it > 3 } assert [1,5,6].sum() == 21 // 利用 plus() 方法对任何数值进行加和运算 assert ['a','e'].sum { it == 'a' ? 1 : it == 'b' ? 2 : it == 'c' ? 3 : it == 'd' ? 4 : it == 'e' ? 5 : 0 // sum 中使用的自定义值 } == 15 assert ['a','e'].sum { ((char) it) - ((char) 'a') } == 10 assert ['a','e'].sum() == 'abcde' assert [['a','b'],['c','d']].sum() == ['a','d'] // 可以提供一个初始化值 assert [].sum(1000) == 1000 assert [1,3].sum(1000) == 1006 assert [1,3].join('-') == '1-2-3' // 字符串连接 assert [1,3].inject('counting: ') { str,item -> str + item // 减少操作 } == 'counting: 123' assert [1,3].inject(0) { count,item -> count + item } == 6 下面是 Groovy 中惯用的一种寻找集合中最大值与最小值的方法: def list = [9,5] assert list.max() == 10 assert list.min() == 2 // 跟任何类似的可对比对象一样,我们也可以比较单个的字符 assert ['x','y','z'].min() == 'a' // 可以使用闭包来指定排序行为 def list2 = ['abc','z','xyzuvw','Hello','321'] assert list2.max { it.size() } == 'xyzuvw' assert list2.min { it.size() } == 'z' 除了闭包之外,还可以使用 Comparator mc = { a,b -> a == b ? 0 : (a < b ? -1 : 1) } def list = [7,-6,-1,-9,-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 添加或去除元素我们可以使用 def list = [] assert list.empty list << 5 assert list.size() == 1 list << 7 << 'i' << 11 assert list == [5,'i',11] list << ['m','o'] assert list == [5,['m','o']] // `<<` 链中的第一项是目标列表 assert ([1,2] << 3 << [4,5] << 6) == [1,[4,5],6] // 使用 `leftShift` 等同于使用 `<<` assert ([1,3] << 4) == ([1,3].leftShift(4)) 我们可以用多种方式为列表添加元素: assert [1,2] + 3 + [4,5] + 6 == [1,6] // 等于调用 `plus` 方法 assert [1,2].plus(3).plus([4,5]).plus(6) == [1,6] def a = [1,3] a += 4 // 创建一个新列表,并以这种方式为 `a` 添加新的元素 a += [5,6] assert a == [1,6] assert [1,*[222,333],456] == [1,222,333,456] assert [*[1,3]] == [1,3] assert [1,[2,6],[8,9]].flatten() == [1,9] def list = [1,2] list.add(3) list.addAll([5,4]) assert list == [1,4] list = [1,2] list.add(1,3) // 把 3 添加到索引为 1 的元素之前 assert list == [1,2] list.addAll(2,[5,4]) //将 [5,4] 添加到索引为 2 的元素之前 assert list == [1,2] list = ['a','u','v','g'] list[8] = 'x' // `[]` 可以当操作符使用,从而按需扩展列表 // 如果需要,也可以在列表中添加 null 值 assert list == ['a','g','x'] 但要提请大家注意的是,列表中的 Groovy Development Kit 中还包含了一些可以通过值来去除列表元素的方法: assert ['a','b'] - 'c' == ['a','b'] assert ['a','b'] - 'b' == ['a','c'] assert ['a','b'] - ['b','c'] == ['a'] def list = [1,1] list -= 3 // 从原始列表中去除 `3` 创建一个新列表 assert list == [1,1] assert ( list -= [2,4] ) == [1,1] 也可以按照索引来去除列表元素,这时列表会发生变化: def list = [1,1] assert list.remove(2) == 3 // 去除第 3 个元素,然后返回该元素 assert list == [1,1] 如果你只想去除列表中第一个跟值相同的元素,而不是所有元素的话,可以使用 def list= ['a','b'] assert list.remove('c') // 去除 'c' 并返回 true,因为元素已经清除了 assert list.remove('b') // 去除第一个 'b' 并返回 true,因为元素已经清除了 assert ! list.remove('z') // 返回 false,因为没有这个元素可供清除 assert list == ['a','b'] 去除列表中所有元素,可以使用 def list= ['a',4] list.clear() assert list == [] 集合操作Groovy Development Kit 中还包含了一些便于操作集合的方法: assert 'a' in ['a','c'] // 如果某一元素属于该列表,则返回 true assert ['a','c'].contains('a') // 等同于 Java 中的 `contains` 方法 assert [1,4].containsAll([1,4]) // `containsAll` 将检查是否已经找到所有的元素 assert [1,5].count(3) == 4 // 计算匹配相应值的元素的数目 assert [1,5].count { it%2==0 // 计算符合谓语要求的元素数目 } == 2 assert [1,12].intersect([1,12]) == [1,12] assert [1,3].disjoint( [4,9] ) assert ![1,3].disjoint( [2,6] ) 排序操作集合往往会用到排序。Groovy 提供了多种列表排序方法,从使用闭包到比较器,如下所示: assert [6,5].sort() == [1,9] def list = ['abc','321'] assert list.sort { it.size() } == ['z','abc','321','xyzuvw'] def list2 = [7,-13] assert list2.sort { a,b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == [-1,-13] Comparator mc = { a,b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } // 只适用于 JDK 8+ // list2.sort(mc) // assert list2 == [-1,-13] def list3 = [6,-3,-7,5] Collections.sort(list3) assert list3 == [-7,9] Collections.sort(list3,mc) assert list3 == [1,9] 复制元素Groovy Development Kit 中利用操作符重载来提供列表元素的复制方法。 assert [1,3] * 3 == [1,3].multiply(2) == [1,3] assert Collections.nCopies(3,'b') == ['b','b'] // JDK 的 nCopies 的语义跟列表所用的 multiply 截然不同 assert Collections.nCopies(2,[1,2]) == [[1,2],2]] // 而不是 [1,2] 2.2 Maps2.2.1 Map 字面量在 Groovy 中,Map(也被称为关联数组)可以通过map 的字面格式( 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 的键默认都是字符串: 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 字面量之外,还可以克隆 map,获得一个新的 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 的浅层副本,如前例所示。 2.2.2 Map 属性表示法Map 也可以像 bean 那样,只要键是字符串这种有效的 Groovy 标识符,就可以使用属性表示法来获取/设置 def map = [name: 'Gromit',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 注意: def map = [name: 'Gromit',id: 1234] assert map.class == null assert map.get('class') == null assert map.getClass() == LinkedHashMap // 这可能是你想要的 map = [1 : 'a',(true) : 'p',(false): 'q',(null) : 'x','null' : 'z'] assert map.containsKey(1) // 1 不是标识符,所以可以这样用 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' 2.2.3 对 map 的迭代Groovy Development Kit,对 map 惯用的迭代会使用 def map = [ Bob : 42,Alice: 54,Max : 33 ] // `entry` 是一个 map 项 map.each { entry -> println "Name: $entry.key Age: $entry.value" } // `entry` 是一个 map 项,`i` 是 map 中的索引 map.eachWithIndex { entry,i -> println "$i - Name: $entry.key Age: $entry.value" } // 当然你还可以直接利用键与值来进行迭代 map.each { key,value -> println "Name: $key Age: $value" } // Key、value 和 i 都可以作为 map 中的索引 map.eachWithIndex { key,value,i -> println "$i - Name: $key Age: $value" } 2.2.4 操纵 map添加或去除元素为 map 添加元素,可以使用 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',4: 'd',13: 'x',15: 't',17: 'u'] 去除 map 中所有元素,可以使用 def m = [1:'a',2:'b'] assert m.get(1) == 'a' m.clear() assert m == [:] 使用 map 字面量格式生成的 map 使用的是 另外值得一提的是,你永远不应使用 def key = 'some key' def map = [:] def gstringKey = "${key.toUpperCase()}" map.put(gstringKey,'value') assert map.get('SOME KEY') == null 键、值与项这里集中审视一下键、值与项的关系。 def map = [1:'a',2:'b',3:'c'] def entries = map.entrySet() entries.each { entry -> assert entry.key in [1,3] assert entry.value in ['a','c'] } def keys = map.keySet() assert keys == [1,3] as Set 上述代码所返回的变异值(map 项、键或值)是非常令人失望的,因为该操作的成功直接跟操作的 map 类型有关。尤其关键的是,Groovy 依靠 JDK 的集合,所以一般无法保证能够安全地通过 过滤与搜索Groovy development kit 所包含的过滤、搜索及收集方法跟 lists 中的差不多。 def people = [ 1: [name:'Bob',age: 32,gender: 'M'],2: [name:'Johnny',age: 36,3: [name:'Claire',age: 21,gender: 'F'],4: [name:'Amy',age: 54,gender:'F'] ] def bob = people.find { it.value.name == 'Bob' } // 查找单独的一个项 def females = people.findAll { it.value.gender == 'F' } // 都能返回项,但可以使用 `collect` 来获取一些信息,比如ages def ageOfBob = bob.value.age def agesOfFemales = females.collect { it.value.age } assert ageOfBob == 32 assert agesOfFemales == [21,54] // 但你也可以使用键/值对作为闭包的参数 def agesOfMales = people.findAll { id,person -> person.gender == 'M' }.collect { id,person -> person.age } assert agesOfMales == [32,36] // 如果所有项都匹配谓语,`every` 就返回 true assert people.every { id,person -> person.age > 18 } // 如果所有项都匹配谓语,`any` 就返回 true assert people.any { id,person -> person.age == 54 } Grouping我们利用一些规则将列表中的各项分组归入一个新创建的 map 中,如下所示: assert ['a',3]].groupBy { it.class } == [(String) : ['a',(Integer) : [7],(ArrayList): [[2,3]] ] assert [ [name: 'Clark',city: 'London'],[name: 'Sharma',[name: 'Maradona',city: 'LA'],[name: 'Zhang',city: 'HK'],[name: 'Ali',[name: 'Liu',].groupBy { it.city } == [ London: [[name: 'Clark',city: 'London']],LA : [[name: 'Maradona',city: 'LA']],HK : [[name: 'Zhang',city: 'HK']],] 2.3. 范围(Ranges)利用范围可以创建一列连续的值。它们可以像 用 用 // 全包含范围 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 = 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 = 1..10 assert range.from == 1 assert range.to == 10 注意,int 类型的范围实现的效率高,创建了一个轻量级的包含了首尾值的 Java 对象。 任何实现了 java.lang.Comparable 接口用于相互比较的 Java 对象都可以使用范围。可以用 // 全包含范围 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') 你还可以利用一个经典的 for (i in 1..10) { println "Hello ${i}" } 但你还可以采用 Groovy 更惯用的风格来做,利用 (1..10).each { i -> println "Hello ${i}" } 范围还可以用在 switch (years) { case 1..10: interestRate = 0.076; break; case 11..25: interestRate = 0.052; break; default: interestRate = 0.037; } 2.4 对集合(collection)的一些语法增强2.4.1 GPath 支持对于列表和 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': 22],null] assert listOfMaps*.a == [11,21,null] // 也适合 null 值 assert listOfMaps*.a == listOfMaps.collect { it?.a } // 等价标记 // 但只收集 非 null 值 assert listOfMaps.a == [11,21] 2.4.2 散布操作符散布操作符还可用于将一个集合内联到另一个中。它是一个语法糖,因为通过它能够避免调用 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',3: 'w'] //在函数参数中的散布 map 标记法 f = { map -> map.c } assert f(*: ['a': 10,'b': 20,'c': 30],'e': 50) == 30 f = { m,i,j,k -> [m,k] } //使用散布 map 标记法来处理未命名与已命名参数 assert f('e': 100,*[4,*: ['a': 10,6) == [["e": 100,"b": 20,"c": 30,"a": 10],6] 2.4.3 星号(
|