这是一篇译文,读者可前往 Groovy Getting Started - The Groovy Development Kit  阅读原文。
1 I/O 
Groovy 为 I/O 提供了大量的便捷方法 。尽管你仍然可以在 Groovy 中使用标准的 Java 代码,但 Groovy 提供了更多方便的途径来处理文件、流等。
具体来说,你应该了解一下添加至如下类的方法:
接下来的内容将重点介绍如何使用上述便捷方法但并不会对所有的这些方法进行完整描述,具体请查阅 GDK API 。
1.1 读取文件 
在第一个例子中,我们先来看看如何在 Groovy 中打印一个文本文件中的内容:
1 2 3 new  File(baseDir, 'haiku.txt' ).eachLine { line ->    println line } 
eachLine 方法是由 Groovy 自动添加到 File 类中的新方法而且有很多的变体,例如如果你想要知道文件的行号,你可以使用如下这个变体:
1 2 3 new  File(baseDir, 'haiku.txt' ).eachLine { line, nb ->    println "Line $nb: $line"  } 
如果 eachLine 的方法体抛出了异常的话,eachLine 方法会确保所有相关资源都被正确地关闭。这一点对于所有由 Groovy 添加的 I/O 方法来说都是相同的。
例如在某些时候你更想使用 Reader,但依然想利用上 Groovy 的自动资源管理。在下面的例子中,即使抛出了异常,所使用的 Reader 依然会被关闭:
1 2 3 4 5 6 7 8 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' )         }     } } 
如果你需要将一个文本文件的每一行内容放入到一个列表中,你可以这样做:
1 def  list = new  File(baseDir, 'haiku.txt' ).collect {it}
或者你也可以使用 as 操作符将文本文件每一行的内容放入到一个数组中:
1 def  array = new  File(baseDir, 'haiku.txt' ) as  String[]
你有试过把文件的内容读入到一个 byte[] 中吗?这么做需要写多少代码呢?Groovy 则使得这么做变得十分简单:
1 byte [] contents = file.bytes
I/O 功能并不局限于文件读写。实际上,很大一部分操作依赖于输入输出流,因此 Groovy 为它们添加了大量的便捷方法,正如你在它们的文档 中看到的那样。
例如,你很容易就能够从一个 File 中获取一个 InputStream:
1 2 3 def  is = new  File(baseDir,'haiku.txt' ).newInputStream()is.close() 
然而,正如你所看到的那样,这样做会需要你自己关闭这个 InputStream。实际上,在 Groovy 中使用 withInputStream 方法来处理资源管理是更好的选择:
1 2 3 new  File(baseDir,'haiku.txt' ).withInputStream { stream ->     } 
1.2 写入文件 
当然了,在某些情况下你可能会想要往文件中写入内容而不是读取内容。其中一种做法是使用 Writer:
1 2 3 4 5 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!'  } 
但对于这么简单的功能,使用 << 运算符也许也足够了:
1 2 3 new  File(baseDir,'haiku.txt' ) << '''Into the ancient pond A frog jumps Water’s sound!''' 
当然了,我们并不总是只需要处理文本内容,所以你也可以使用 Writer 或者像如下示例那样直接写入字节:
当然,你也可以直接处理输出流。例如,你可以像这个样子来创建一个能写入到文件的输出流:
1 2 3 def  os = new  File(baseDir,'data.bin' ).newOutputStream()os.close() 
然而,正如你所见,这么做需要你自己关闭该输出流。同样,使用 withOutputStream 方法是更好的做法,因为它能处理抛出的异常并最终能在任何情况下关闭输出流:
1 2 3 new  File(baseDir,'data.bin' ).withOutputStream { stream ->     } 
1.3 遍历文件树 
在编写脚本的时候我们经常会需要遍历文件树来找到某些特定的文件并进行一些处理。Groovy 为此提供了多种不同的方法。例如你可以对文件夹中的所有文件执行指定的操作:
1 2 3 4 5 6 dir.eachFile { file ->                           println file.name } dir.eachFileMatch(~/.*\.txt/ ) { file ->          println file.name } 
    
         
<tr>
    <td>1</td>
    <td>对文件夹中的所有文件执行给定的闭包代码</td>
</tr>
<tr>
    <td>2</td>
    <td>对文件夹中所有匹配指定模式的文件执行给定的闭包代码</td>
</tr>
有时你还需要处理更深的文件层次,这时候你就需要使用 eachFileRecurse 了:
1 2 3 4 5 6 7 dir.eachFileRecurse { file ->                           println file.name } dir.eachFileRecurse(FileType.FILES) { file ->           println file.name } 
    
         
<tr>
    <td>1</td>
    <td>从该目录开始递归地查找所有文件或目录并执行指定的闭包代码</td>
</tr>
<tr>
    <td>2</td>
    <td>从该目录开始递归地查找所有文件并执行指定的闭包代码</td>
</tr>
对于更复杂的遍历操作你可以使用 traverse 方法,这需要你要返回特殊的标识位来指示如何进行遍历:
1 2 3 4 5 6 7 8 9 dir.traverse { file ->     if  (file.directory && file.name=='bin' ) {         FileVisitResult.TERMINATE                        } else  {         println file.name         FileVisitResult.CONTINUE                         } } 
    
         
    
        1 
        如果该文件为一个文件夹且名称为 `bin` 则停止遍历 
     
    
        2 
        否则打印文件的名称并继续遍历 
     
1.4 数据与对象 
在 Java 中,通过 java.io.DataOutputStream 和 java.io.DataInputStream 类来对数据进行序列化和反序列化并不少见,而 Groovy 则让这个过程变得更为简单。例如,你可以使用如下代码来将数据序列化到文件中并读取:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 } 
同样的,如果你想要序列化的数据实现了 Serializable 接口,你还可以像如下代码那样使用 ObjectOutputStream:
1 2 3 4 5 6 7 8 9 10 11 12 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 和输入输出流有多简便。然而,在诸如系统管理或者 DevOps 这样的领域中,我们则需要 Groovy 脚本能够与外部进程进行通信。
Groovy 提供了一种十分简单的方法来执行命令行进程,只要把命令行写作一个简单的 String 对象然后调用其 execute() 方法即可。例如,在一个 *nix 机器上(或者一个安装了合适的 *nix 命令行环境的 Windows 机器上),你可以这样做:
1 2 def  process = "ls -l" .execute()             println "Found text ${process.text}"          
    
         
    
        1 
        在一个外部进程中执行 `ls` 命令 
     
    
        2 
        消耗命令的输出并将其作为文本进行读取 
     
execute() 方法会返回一个 java.lang.Process 对象,借由此我们可以对标准输入/标准输出/错误输出流进行处理,或者检查进程退出时的退出值。
例如,这里我们执行与上例相同的命令,但我们将逐行地处理其输出流:
1 2 3 4 def  process = "ls -l" .execute()             process.in .eachLine { line ->                    println line                             } 
    
         
    
        1 
        在一个外部进程中执行 `ls` 命令 
     
    
        2 
        对于该进程的输入流中的每一行内容 
     
    
        3 
        输出该内容 
     
值得注意的是 in 代表的输入流对应着命令的标准输出,而你可以通过 out 代表的输出流向进程的标准输入写入数据。
注意,有不少命令实际上是 Shell 的内置功能,需要一些特殊的处理。所以如果你想要在一个 Windows 机器上列出一个文件夹内的所有文件,然后这样写的话:
1 2 def  process = "dir" .execute()println "${process.text}"  
你会得到一个 IOException,内容如下:Cannot run program "dir": CreateProcess error=2, The system cannot find the file specified.
这是因为 dir 实际上是 Windows Shell(cmd.exe)的一个内置功能,不能被当做一个单纯的可执行文件来运行。因此,你应该这样写:
1 2 def  process = "cmd /c dir" .execute()println "${process.text}"  
除此之外,由于这个功能实际上是通过 java.lang.Process 实现的,因此我们也应该考虑到这个类的一些不足之处。具体来说,它的 JavaDoc 是这么说的:
因为有些平台只为标准输入和输出流提供了很有限的缓存空间,写入输入流和读取输出流发生错误时可能会导致子进程发生阻塞,甚至发生死锁。
 
正是因为这个原因,Groovy 提供了一些额外的便捷方法来更好地处理外部进程的输入输出流。
通过如下代码你可以消耗掉进程的所有输出(包括错误流输出):
1 2 3 def  p = "rm -f foo.tmp" .execute([], tmpDir)p.consumeProcessOutput() p.waitFor() 
consumeProcessOutput 方法还包括其他一些变体可以利用 StringBuffer、InputStream、OutputStream 等,详见 java.lang.Process 的 GDK API 。
除此之外,还有一个 pipeTo 方法(对应于 | 操作符且可进行重载)可以将一个进程的输出流内容转移到另一个进程的输入流中。
如下为使用该方法的案例。
1 2 3 4 5 6 7 8 9 10 11 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 } 
消耗错误流输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 为各种不同的集合类型提供了原生的语言支持,包括列表 、映射 和范围 。这些集合类大多数都基于 Java 原本的集合类型,同时加上了 GDK  特有的方法。
2.1 列表 2.1.1 列表字面量 
你可以像如下代码那样创建列表。注意 [] 是空列表表达式。
1 2 3 4 5 6 7 8 9 def  list = [5 , 6 , 7 , 8 ]assert  list.get(2 ) == 7 assert  list[2 ] == 7 assert  list instanceof  java.util.Listdef  emptyList = []assert  emptyList.size() == 0 emptyList.add(5 ) assert  emptyList.size() == 1 
每一个列表表达式都会创建一个 java.util.List
当然,列表也可以用于创建另一个列表:
1 2 3 4 5 6 7 8 9 def  list1 = ['a' , 'b' , 'c' ]def  list2 = new  ArrayList<String>(list1)assert  list2 == list1 def  list3 = list1.clone()assert  list3 == list1
列表实际上就是对象的有序集合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def  list = [5 , 6 , 7 , 8 ]assert  list.size() == 4 assert  list.getClass() == ArrayList     assert  list[2 ] == 7                      assert  list.getAt(2 ) == 7                assert  list.get(2 ) == 7                  list[2 ] = 9  assert  list == [5 , 6 , 9 , 8 ,]            list.putAt(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.5 f, 2.5 d, 'hello' , 7 g, null , 9  as  byte ]assert  [1 , 2 , 3 , 4 , 5 ][-1 ] == 5              assert  [1 , 2 , 3 , 4 , 5 ][-2 ] == 4 assert  [1 , 2 , 3 , 4 , 5 ].getAt(-2 ) == 4        try  {    [1 , 2 , 3 , 4 , 5 ].get(-2 )                      assert  false  } catch  (e) {     assert  e instanceof  ArrayIndexOutOfBoundsException } 
2.1.2 将列表作为布尔表达式 列表可以被估作一个 boolean 值:
1 2 3 4 assert  ![]             assert  [1 ] && ['a' ] && [0 ] && [0.0 ] && [false ] && [null ]
2.1.3 遍历列表 通常我们可以通过调用 each 或 eachWithIndex 方法来遍历列表的所有元素并给定处理元素的代码:
1 2 3 4 5 6 [1 , 2 , 3 ].each {     println "Item: $it"   } ['a' , 'b' , 'c' ].eachWithIndex { it, i ->      println "$i: $it"  } 
除了遍历列表,有时我们还需要对一个列表的元素进行转换进而构建出另一个新的列表。这个操作,又被称为映射,在 Groovy 中可通过 collect 方法完成:
1 2 3 4 5 6 7 8 9 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 ]assert  [1 , 2 , 3 ].collect(list) { it * 2  } == [0 , 2 , 4 , 6 ]assert  list == [0 , 2 , 4 , 6 ]
2.1.4 过滤和查找 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 assert  [1 , 2 , 3 ].find { it > 1  } == 2                assert  [1 , 2 , 3 ].findAll { it > 1  } == [2 , 3 ]       assert  ['a' , 'b' , 'c' , 'd' , 'e' ].findIndexOf {          it in  ['c' , 'e' , 'g' ] } == 2  assert  ['a' , 'b' , 'c' , 'd' , 'c' ].indexOf('c' ) == 2   assert  ['a' , 'b' , 'c' , 'd' , 'c' ].indexOf('z' ) == -1  assert  ['a' , 'b' , 'c' , 'd' , 'c' ].lastIndexOf('c' ) == 4 assert  [1 , 2 , 3 ].every { it < 5  }                assert  ![1 , 2 , 3 ].every { it < 3  }assert  [1 , 2 , 3 ].any { it > 2  }                     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 还提供了在集合中查找最大值和最小值的方法:
1 2 3 4 5 6 7 8 9 10 11 def  list = [9 , 4 , 2 , 10 , 5 ]assert  list.max() == 10 assert  list.min() == 2 assert  ['x' , 'y' , 'a' , 'z' ].min() == 'a' def  list2 = ['abc' , 'z' , 'xyzuvw' , 'Hello' , '321' ]assert  list2.max { it.size() } == 'xyzuvw' assert  list2.min { it.size() } == 'z' 
除了闭包,你还可以使用 Comparator 来定义大小比较规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 
2.1.5 添加和移除元素 我们可以使用 [] 来创建一个新的空列表并用 << 来向其中追加元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def  list = []assert  list.emptylist << 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' ]]assert  ([1 , 2 ] << 3  << [4 , 5 ] << 6 ) == [1 , 2 , 3 , [4 , 5 ], 6 ]assert  ([1 , 2 , 3 ] << 4 ) == ([1 , 2 , 3 ].leftShift(4 ))
除此之外很有很多种向列表中添加元素的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 assert  [1 , 2 ] + 3  + [4 , 5 ] + 6  == [1 , 2 , 3 , 4 , 5 , 6 ]assert  [1 , 2 ].plus(3 ).plus([4 , 5 ]).plus(6 ) == [1 , 2 , 3 , 4 , 5 , 6 ]def  a = [1 , 2 , 3 ]a += 4        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 )  assert  list == [1 , 3 , 2 ]list.addAll(2 , [5 , 4 ])  assert  list == [1 , 3 , 5 , 4 , 2 ]list = ['a' , 'b' , 'z' , 'e' , 'u' , 'v' , 'g' ] list[8 ] = 'x'   assert  list == ['a' , 'b' , 'z' , 'e' , 'u' , 'v' , 'g' , null , 'x' ]
然而,值得注意的是,对列表使用 + 运算符并不会改变原列表。比起 <<,它会产生出一个新的列表,很多时候这可能不是你想要的效果进而带来一些性能上的问题。
GDK 同样包含一些可以让你很方便地从列表中移除元素的方法:
1 2 3 4 5 6 7 8 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             assert  list == [1 ,2 ,4 ,2 ,1 ]assert  ( list -= [2 ,4 ] ) == [1 ,1 ]
同样,我们还可以通过给定元素的索引值来移除元素,而这种情况则会改变原本的列表:
1 2 3 def  list = [1 ,2 ,3 ,4 ,5 ,6 ,2 ,2 ,1 ]assert  list.removeAt(2 ) == 3           assert  list == [1 ,2 ,4 ,5 ,6 ,2 ,2 ,1 ]
如果你只是想移除列表中第一个拥有给定值的元素而不是移除所有元素,你可以使用 remove 方法:
1 2 3 4 5 6 def  list= ['a' ,'b' ,'c' ,'b' ,'b' ]assert  list.remove('c' )             assert  list.remove('b' )             assert  ! list.remove('z' )           assert  list == ['a' ,'b' ,'b' ]
通过 clear 方法可以移除列表中的所有元素:
1 2 3 def  list= ['a' ,2 ,'c' ,4 ]list.clear() assert  list == []
2.1.6 集合操作 GDK 还提供了可以更好地进行集合操作的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 assert  'a'  in  ['a' ,'b' ,'c' ]             assert  ['a' ,'b' ,'c' ].contains('a' )      assert  [1 ,3 ,4 ].containsAll([1 ,4 ])       assert  [1 ,2 ,3 ,3 ,3 ,3 ,4 ,5 ].count(3 ) == 4   assert  [1 ,2 ,3 ,3 ,3 ,3 ,4 ,5 ].count {    it%2 ==0                               } == 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 ] )assert  ![1 ,2 ,3 ].disjoint( [2 ,4 ,6 ] )
2.1.7 排序 
使用集合时通常需要对其进行排序。Groovy 同样提供了多种排序列表的方式,可以使用闭包或是提供 Comparator,正如如下例子所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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  } 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 ]
2.1.8 复制元素 
GDK 还利用了运算符重载的功能为列表提供了复制元素的方法:
1 2 3 4 5 6 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' ]assert  Collections.nCopies(2 , [1 , 2 ]) == [[1 , 2 ], [1 , 2 ]] 
2.2 映射 2.2.1 映射字面量 
在 Groovy 中,映射(又被称为联合数组)可使用映射字面量语法 [:] 创建:
1 2 3 4 5 6 7 8 9 10 11 12 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.Mapdef  emptyMap = [:]assert  emptyMap.size() == 0 emptyMap.put("foo" , 5 ) assert  emptyMap.size() == 1 assert  emptyMap.get("foo" ) == 5 
映射的键默认为字符串:[a:1] 等价于 ['a':1]。你有可能会没能意识到这种语句的含义,如果你定义了一个叫 a 的变量并且你想将它的值作为映射的键的话。如果你想要这样做的话,你应该像下面的例子那样为键加上括号来进行转义:
1 2 3 4 5 6 7 def  a = 'Bob' def  ages = [a:  43 ]assert  ages['Bob' ] == null  assert  ages['a' ] == 43      ages = [(a): 43 ]             assert  ages['Bob' ] == 43    
除了映射字面量,你还可以获取一个映射的拷贝:
1 2 3 4 5 6 7 8 9 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 
正如上面的例子所示,所得的映射只是原映射的浅 拷贝。
2.2.2 映射属性访问语句 
映射同样可以作为 Bean 使用,因此你也可以使用属性访问语句来访问映射,只要映射的键是字符串而且也是合法的 Groovy 标识符:
1 2 3 4 5 6 7 8 9 def  map = [name:  'Gromit' , likes:  'cheese' , id:  1234 ]assert  map.name == 'Gromit'      assert  map.id == 1234 def  emptyMap = [:]assert  emptyMap.size() == 0 emptyMap.foo = 5  assert  emptyMap.size() == 1 assert  emptyMap.foo == 5 
注意,按这种规则的话,map.foo 会导致 Groovy 从映射 map 中查找 foo。这意味着如果映射 map 不包含键 class 的话,map.class 会返回 null。如果你只是想要获取映射的 Class 对象,你只能直接使用 getClass() 方法 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def  map = [name:  'Gromit' , likes:  'cheese' , 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 ) 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 遍历映射 正如之前那样,GDK 同样为映射提供了 each 和 eachWithIndex 方法来进行遍历。值得注意的是通过映射字面量表达式创建的映射是有序的,也就是说如果你尝试遍历映射,映射中的键值对将总是以其被添加到映射中的顺序被遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def  map = [        Bob  :  42 ,         Alice:  54 ,         Max  :  33  ] map.each { entry ->     println "Name: $entry.key Age: $entry.value"  } map.eachWithIndex { entry, i ->     println "$i - Name: $entry.key Age: $entry.value"  } map.each { key, value ->     println "Name: $key Age: $value"  } map.eachWithIndex { key, value, i ->     println "$i - Name: $key Age: $value"  } 
2.2.4 添加和删除元素 
可以通过 put 方法、putAll 方法或下标运算符来将一个元素添加到映射中:
1 2 3 4 5 6 7 8 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' ]
调用 clear 方法可以移除映射中的所有元素:
1 2 3 4 def  m = [1 :'a' , 2 :'b' ]assert  m.get(1 ) == 'a' m.clear() assert  m == [:]
由映射字面量语法产生的映射依赖于键的 equals 和 hashCode 方法,因此你不应使用那些 hashCode 会发生变化的对象作为键,否则你很有可能无法获取到其关联的值。
除此之外值得注意的是,你不应使用 GString 作为映射的键,因为 GString 的哈希码和内容与其相同的 String 的哈希码是不同的:
1 2 3 4 5 def  key = 'some key' def  map = [:]def  gstringKey = "${key.toUpperCase()}" map.put(gstringKey,'value' ) assert  map.get('SOME KEY' ) == null 
2.1.5 键、值与键值对 
我们可以在一个视图中读取映射的键、值和键值对:
1 2 3 4 5 6 7 8 9 10 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
通过该试图来修改映射(修改其键或值或键值对)都是不可取的,因为这样的操作是否能顺利执行直接取决于其背后被修改的映射的类型。具体来说,Groovy 所使用的来自 JDK 的集合类并不保证映射可以安全地通过其 keySet、entrySet 或 values 视图进行修改。
2.1.6 过滤与查找 
GDK 也为映射提供了与列表 类似的过滤、查找和收集方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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'  } def  females = people.findAll { it.value.gender == 'F'  }def  ageOfBob = bob.value.agedef  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 ]assert  people.every { id, person ->    person.age > 18  } assert  people.any { id, person ->    person.age == 54  } 
2.1.7 分组 
我们可以通过给定一个条件来让列表中的元素各自分组形成一个列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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' ]], ] 
2.3 区间 
你可以使用区间(Range)来创建一个由连续值组成的列表。区间可以被直接用作 List 因为 Rangejava.util.List
使用 .. 记号定义的区间是一个闭区间(也就是说该列表包含了起始值和终止值)。
使用 ..< 记号定义的区间则是一个半开区间:它包含起始值但不包含终止值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def  range = 5. .8 assert  range.size() == 4 assert  range.get(2 ) == 7 assert  range[2 ] == 7 assert  range instanceof  java.util.Listassert  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.Listassert  range.contains(5 )assert  !range.contains(8 )range = 1. .10  assert  range.from == 1 assert  range.to == 10 
值得注意的是,int 类型区间的实现方式十分高效,实际上就是一个只包含了起始值和终止值的 Java 对象。
区间可以被用作任何实现了 java.lang.Comparable 接口用于进行大小比较,同时又有方法 next() 和 previous() 用于返回其上一个和下一个值的 Java 对象。例如,你可以创建一个由 String 元素组成的区间:
1 2 3 4 5 6 7 8 9 def  range = 'a' ..'d' assert  range.size() == 4 assert  range.get(2 ) == 'c' assert  range[2 ] == 'c' assert  range instanceof  java.util.Listassert  range.contains('a' )assert  range.contains('d' )assert  !range.contains('e' )
你可以使用经典的 for 循环来迭代区间:
1 2 3 for  (i in  1. .10 ) {    println "Hello ${i}"  } 
但你也可以通过使用 each 方法来更 Groovy 地迭代区间:
1 2 3 (1. .10 ).each { i ->     println "Hello ${i}"  } 
区间还可用于 switch 语句:
1 2 3 4 5 switch  (years) {    case  1. .10 : interestRate = 0.076 ; break ;     case  11. .25 : interestRate = 0.052 ; break ;     default:  interestRate = 0.037 ; } 
2.4 集合类的语法增强 2.4.1 GPath 支持 多亏了列表和映射都支持属性访问语法,在 Groovy 中我们可以使用语法糖来更好地应对嵌套集合,如下例所示:
1 2 3 4 5 6 7 8 9 def  listOfMaps = [['a' : 11 , 'b' : 12 ], ['a' : 21 , 'b' : 22 ]]assert  listOfMaps.a == [11 , 21 ] 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 ] 
2.4.2 延伸运算符 
延伸运算符可用于将一个集合“内联”到另一个集合之中。这个语法糖主要为了使我们不需要调用 putAll 方法并能写出更简短的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 assert  [ 'z' : 900 ,         *: ['a' : 100 , 'b' : 200 ], 'a' : 300 ] == ['a' : 300 , 'b' : 200 , 'z' : 900 ] 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' ]f = { map -> map.c } assert  f(*: ['a' : 10 , 'b' : 20 , 'c' : 30 ], 'e' : 50 ) == 30 f = { m, i, j, k -> [m, i, j, k] } assert  f('e' : 100 , *[4 , 5 ], *: ['a' : 10 , 'b' : 20 , 'c' : 30 ], 6 ) ==        [["e" : 100 , "b" : 20 , "c" : 30 , "a" : 10 ], 4 , 5 , 6 ] 
2.4.3 *. 运算符 
星点运算符可用于调用集合中所有元素的某个方法或属性:
1 2 3 4 5 6 7 8 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
2.4.4 使用下标运算符进行分割 
你可以使用下标运算符根据索引值来访问列表、元素和映射的元素。有趣的是在这种情况下,字符串也会被视作特殊的集合:
1 2 3 4 5 6 7 8 9 10 11 12 def  text = 'nice cheese gromit!' def  x = text[2 ]assert  x == 'c' assert  x.class  == Stringdef  sub = text[5. .10 ]assert  sub == 'cheese' def  list = [10 , 11 , 12 , 13 ]def  answer = list[2 ,3 ]assert  answer == [12 ,13 ]
值得注意的是你可以使用区间来获取集合中的一小部分:
1 2 3 list = 100. .200  sub = list[1 , 3 , 20. .25 , 33 ] assert  sub == [101 , 103 , 120 , 121 , 122 , 123 , 124 , 125 , 133 ]
对于那些可变的集合,下标运算符可用于更新集合的值:
1 2 3 list = ['a' ,'x' ,'x' ,'d' ] list[1. .2 ] = ['b' ,'c' ] assert  list == ['a' ,'b' ,'c' ,'d' ]
除此之外,你还可以使用负索引值来更好地从集合末尾开始提取元素:
1 2 3 4 5 6 text = "nice cheese gromit!"  x = text[-1 ] assert  x == "!" def  name = text[-7. .-2 ]assert  name == "gromit" 
最后,如果你使用的是一个反向区间(起始值大于终止值),那么所得的结果也是反向的:
1 2 3 text = "nice cheese gromit!"  name = text[3. .1 ] assert  name == "eci" 
2.5 新添加的集合方法 
除了列表 、映射 和区间 以外,Groovy 还为其他集合或更普通的 Iterable 类提供了更多的用于过滤、收集、分组、计数等方法。
有关这方面的内容,我们希望你能仔细阅读 GDK  的 API 文档。具体来说:
在这里 可以找到 Iterable 的新方法 
在这里 可以找到 Iterator 的新方法 
在这里 可以找到 Collection 的新方法 
在这里 可以找到 List 的新方法 
在这里 可以找到 Map 的新方法 
 
3 其他好用的功能 3.1 ConfigSlurper 
ConfigSlurper 是可用于读取以 Groovy 脚本形式编写的配置文件的功能类。正如 Java 的 *.properties 文件那样,ConfigSlurper 也可以使用点号语法进行访问,除此之外它还能用闭包括号来给定配置值以及任意的对象类型:
1 2 3 4 5 6 7 8 9 10 11 def  config = new  ConfigSlurper().parse('''     app.date = new Date()  // 注1     app.age  = 42     app {                  // 注2         name = "Test${42}"     } ''' )assert  config.app.date instanceof  Dateassert  config.app.age == 42 assert  config.app.name == 'Test42' 
使用点号语法 
使用闭包括号语法替代点号语法 
 
正如我们在上一个例子中所见到的那样,parse 方法可用于获取一个 groovy.util.ConfigObject 实例。ConfigObject 是一种特殊的 java.util.Map 实现类,它要么返回具体的配置值要么返回一个新的 ConfigObject,但绝不会返回 null。
1 2 3 4 5 6 7 def  config = new  ConfigSlurper().parse('''     app.date = new Date()     app.age  = 42     app.name = "Test${42}" ''' )assert  config.test != null    
我们并未给出 `config.test`,但在被调用时仍然返回了一个 `ConfigObject`
 
如果点号本身需要作为配置变量的名称的话,可以使用单引号或双引号对其进行转义:
1 2 3 4 5 def  config = new  ConfigSlurper().parse('''     app."person.age"  = 42 ''' )assert  config.app."person.age"  == 42 
除此之外,ConfigSlurper 还支持不同的环境。environments 方法可被用于处理一个包含若干个配置小节的 Closure 实例。假设我们想要为开发环境创建一些特别的配置值。那么在创建 ConfigSlurper 实例时我们可以使用 ConfigSlurper(String) 构造器来给定目标环境:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def  config = new  ConfigSlurper('development' ).parse('''   environments {        development {            app.port = 8080        }        test {            app.port = 8082        }        production {            app.port = 80        }   } ''' )assert  config.app.port == 8080 
ConfigSlurper 支持的环境并不只局限于几个具体的环境名,它取决于 ConfigSlurper 的客户端代码支持的环境并能基于此进行解析。
environments 方法本身是内置的,但你同样可以通过 registerConditionalBlock 来注册除了 environments 以外的方法名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def  slurper = new  ConfigSlurper()slurper.registerConditionalBlock('myProject' , 'developers' )    def  config = slurper.parse('''   sendMail = true   myProject {        developers {            sendMail = false        }   } ''' )assert  !config.sendMail
在注册了新的代码块以后,ConfigSlurper 就能进行解析了 
 
在与 Java 进行整合时,我们可以使用 toProperties 方法将 ConfigObject 转换成一个 java.util.Properties,然后再将其存储至一个 *.properties 文本文件中。但要注意的是在转换成新的 Properties 实例的时候所有配置值都会被转换为 String 实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 def  config = new  ConfigSlurper().parse('''     app.date = new Date()     app.age  = 42     app {         name = "Test${42}"     } ''' )def  properties = config.toProperties()assert  properties."app.date"  instanceof  Stringassert  properties."app.age"  == '42' assert  properties."app.name"  == 'Test42' 
3.2 Expando 
Expando 类可用于创建一个可动态扩展的对象。尽管它的名字看起来很像,但实际上它并没有利用 ExpandoMetaClass 来实现。每个 Expando 对象都代表一个独立的、可动态构造的实例,这些实例可在运行时用属性或方法进行扩展。
1 2 3 4 def  expando = new  Expando()expando.name = 'John'  assert  expando.name == 'John' 
当将一个闭包代码块注册为动态属性时则比较特殊:在完成注册后可以像调用方法那样对其进行调用:
1 2 3 4 5 6 def  expando = new  Expando()expando.toString = { -> 'John'  } expando.say = { String s -> "John says: ${s}"  } assert  expando as  String == 'John' assert  expando.say('Hi' ) == 'John says: Hi' 
3.3 可观察的列表、映射和集 
Groovy 还提供了可观察的列表、映射和集。这些集合在添加、移除或修改元素时都会触发 java.beans.PropertyChangeEvent 事件。值得注意的是一个 PropertiChangeEvent 并不只用于告诉监听器发生了特定的事件,它还包含了包括属性名以及属性修改前后的值等内容。
根据所发生的修改的类型,可观察的集合甚至可以一次触发多个不同类型的 PropertyChangeEvent 事件。例如,向一个可观察的列表中添加一个元素会触发 ObservableList.ElementAddedEvent 事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def  event                                       def  listener = {    if  (it instanceof  ObservableList.ElementEvent)  {           event = it     } } as  PropertyChangeListener def  observable = [1 , 2 , 3 ] as  ObservableList    observable.addPropertyChangeListener(listener)   observable.add 42                                 assert  event instanceof  ObservableList.ElementAddedEventdef  elementAddedEvent = event as  ObservableList.ElementAddedEventassert  elementAddedEvent.changeType == ObservableList.ChangeType.ADDEDassert  elementAddedEvent.index == 3 assert  elementAddedEvent.oldValue == null assert  elementAddedEvent.newValue == 42 
声明一个 PropertyChangeEventListener 用于捕获触发的事件 
ObservableList.ElementEvent 及其子类都会使该监听器起作用注册监听器 
用给定的列表创建一个 ObservableList 
触发一个 ObservableList.ElementAddedEvent 事件 
 
注意,添加元素实际上会触发两个事件。第一个事件即为 ObservableList.ElementAddedEvent,而第二个实为一个 PropertyChangeEvent,用于告诉监听器列表的大小属性发生了变化。
ObservableList.ElementClearedEvent 则是另一种比较有意思的事件。当列表中的复数元素被移除,例如被调用了 clear() 方法时,它会包含所有被从列表中移除的元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def  eventdef  listener = {    if  (it instanceof  ObservableList.ElementEvent)  {         event = it     } } as  PropertyChangeListener def  observable = [1 , 2 , 3 ] as  ObservableListobservable.addPropertyChangeListener(listener) observable.clear() assert  event instanceof  ObservableList.ElementClearedEventdef  elementClearedEvent = event as  ObservableList.ElementClearedEventassert  elementClearedEvent.values == [1 , 2 , 3 ]assert  observable.size() == 0 
为更好地了解所有支持的事件类型,读者可以参考所使用的可观察集合的 JavaDoc 文档或源代码。
ObservableMap 和 ObservableSet 同样包含了在这节中我们所看到的 ObservableList 所包含的功能。