11.3 Groovy开发包(GDK)
Groovy自带了一个庞大的类库,你可使用它来简化工作。在这个类库中,有些类提供了新功能,而有些是Java类库中类的包装器,旨在让Java类使用起来更容易或改进它们的功能。本节介绍Groovy运行时库中的一些重要类和类型。Groovy运行时库是随Groovy一起安装的,有时也被称为Groovy开发包(GDK)。
11.3.1 Groovy字符串(GString)
Groovy提供了java.lang.String类的变种——groovy.lang.GString。每当你使用双引号创建字符串时,Groovy都会检查你是否使用了GString的功能。如果使用了,它就创建GString实例,否则创建java.lang.String实例:
def s = "this is an ordinary java.lang.String instance";
这个字符串没有使用GString的功能,因此Groovy创建一个java.lang.String。GString最有用的功能之一是提供了内置的模板支持:
def who = "you"def msg = "Happy birthday to $who"
运行上述代码时,msg将包含Happy birthday to you。由于在字符串中使用了变量,因此上述代码将创建GString。使用模板变量时,必须使用大括号来消除二义性:
def who2 = "packtpub"def msg2 = "Please visit ${who2}.com"
如果省略大括号,上述代码将崩溃,因为who2指向一个java.lang.String实例,而这种实例没有属性com。
要在字符串中使用美元符号,一种办法是使用反斜杠对其进行转义:"US\$ 100"表示字符串US$100。另一种选择是创建Java字符串,在Groovy,这可使用单引号来实现:
def javaString = 'This is a Java string, even though it has ${who}'
上述代码将创建一个java.lang.String,同时不会将${variable}替换为相应的变量。这与Java不同;Java用单引号来表示char字面量值。
当你将类型声明为char(或其包装类java.lang.Character)时,Groovy将创建java.lang.Character实例(别忘了,Groovy在任何情况下都不会创建基本类型值):
char c = 'C'c.class
上述代码片段的结果为java.lang.Character。请注意,代码char c = "C"(使用双引号)也将创建一个java.lang.Character实例,但在Java语言中,这样的代码会导致编译错误。
最后,与众多其他的现代语言一样,Groovy也支持多行字符串(GString和Java字符串都如此):
def longMsg = """Happy birthdayto ${who}"""
与单行的GString实例一样,多行的GString实例也支持变量。要创建多行的Java字符串,可使用三个单引号:
def longJavaMsg = '''Another longmessage'''
最后,需要指出的是,在Groovy中可使用运算符==和!=对字符串进行比较。在这种情况下,Groovy将调用方法equals()来比较字符串的内容:
def s1 = "hello"def s2 = 'hello'println(s1 == s2)
在这个示例中,两个字符串都是java.lang.String实例(因为没有使用GString的功能)。如果运行这些代码,将向控制台打印true。即便s1是一个包含"hello"的GString的实例,也将打印true。
11.3.2 集合
在集合方面,Groovy将类似于Python的体验带到了JVM中。对于Java类库中最重要的集合,它提供了内置的支持,同时给它们添加了额外的功能。本节将介绍如下主题:
- 列表;
- 映射。
与Java相比,最大的不同在于Groovy集合不支持泛型。虽然Groovy解析器支持Java泛型语法,但Groovy不会以任何方式实现泛型。
- 列表
要创建列表(java.util.ArrayList实例),只需使用中括号即可:
def list = [10, 20, 30, 40, 50]
要获取列表中的元素,可使用中括号并在其中指定索引:
println list[1]
这将返回20。在这个示例中,这行代码与list.get(1)完全等价。但存在一个差别:如果指定的索引不在有效范围内,使用中括号时结果将为null,而使用方法get()时将引发IndexOutOfBoundsException异常。
使用中括号时,可指定负索引以倒数的方式获取元素:-1表示最后一个元素,-2表示倒数第二个元素,以此类推。请看下面的示例:
println list[-4]
这行代码返回20。请注意,在方法get()中不能指定负索引,否则将引发IndexOutOf BoundsException异常。
要对列表执行切片操作,可使用Groovy的下标运算符..(两个句点):
println list[1..2]
指定的两个索引都包含在切片内,因此这将返回[20, 30]。
除指定范围外,还可指定一系列索引:
println list[0,3]
这将返回[10, 40]。另外,还可同时指定范围和特定的索引:
println list[0..2, 4, 3]
这将返回[10, 20, 30, 50, 40]。
在真正的动态编程中,空列表对应false,而包含元素的列表对应true。可利用这一点来检查列表是否是空的:
def emptyList = []if (!emptyList) {println("List is not empty")}
上述代码不会打印任何输出。在Java 8推出之前很久,Groovy就给Java集合类添加了函数式编程功能。要遍历集合,可指定一个闭包:
list.each({def bar = "X" * itprintln "${bar} ${it}"})
将对每个元素调用这个闭包,而变量it包含当前元素的值。在这个闭包中,创建了变量bar,它包含将X重复n次的结果(其中n为当前元素的值)。上述代码的输出如下:
XXXXXXXXXX 10XXXXXXXXXXXXXXXXXXXX 20XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 30XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 40XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 50
包括java.util.ArrayList在内的大多数集合类都继承了接口java.util.Collection,而在这个接口(及其继承的接口)中,Groovy添加了一些非常方便的方法,下表列出了其中的几个。
方法名描述示例 any(Closure)如果至少有一个列表元素导致闭包返回true,这个方法就返回truelist.any { it > 20 }
返回true every(Closure)如果所有的列表元素都导致闭包返回true,这个方法就返回true,否则返回falselist.every { it < 50 }
返回false find(Closure)对每个元素调用闭包;如果闭包返回true,将停止迭代。如果找到指定的元素,就返回它;否则返回nulllist.find { it == 30}
返回30 findAll(Closure)类似于find(),但不在闭包返回true时停止迭代list.findAll { it > 30}
返回[40, 50] join(String)将所有元素合并成一个字符串,并用指定的字符分隔元素list.join("/")
返回10/20/30/40/50 min()返回最小的元素list.min()
返回10 max()返回最大的元素list.max()
返回50 sum()返回所有元素之和list.sum()
返回150
- 映射
要创建映射,可使用中括号并在其中指定键-值对:
def map = [ key1: "value1", "key2": "value2" ]
键的默认类型为字符串,因此指定键时,可以不将其用引号括起。在需要添加基于变量的键或类型不是字符串的键时,这可能是个问题;在这些情况下,必须使用括号将键括起:
def key1 = "whateverKey"def otherMap = [ (key1): "whateverValue"]
要创建空的映射,必须使用如下表示法:
def emptyMap = [:]
要读写映射,可使用中括号:
map["key1"] = "anotherValue1"println map["key1"]
Groovy将映射视为POJO,因此可使用句点表示法来读写映射:
map.key1 = "yetAnotherValue1"println(map.key1)
仅当键为字符串(合法的Java标识符)时,这种表示法才可行。如果键不是字符串,就必须使用中括号或方法get:
map[30] = "thirty"println(map.get(30))
由于30不是字符串(不是合法的Java标识符),因此必须使用映射的方法get()或中括号表示法来访问它。
由于接口Map继承了接口Collection,因此映射支持前面介绍的所有方法。不同之处在于,闭包将一个MapEntry对象作为参数,并通过其属性key和value来获取每个元素的键和值。
来看一些遍历键-值对的示例:
map.each({println("$it.key --> $it.value")})
在闭包中也可使用键-值对。在这种情况下,必须给闭包指定两个参数:
map.find({ k, v -> k =="key2" && v == "value2" })
在这个示例中,参数k和v分别表示映射中每个元素的键和值。
与Java相比,最大的不同在于Groovy集合不支持泛型。虽然Groovy解析器支持Java泛型语法,但Groovy不会以任何方式实现泛型。