Groovy 语言规范 - 第一章:语法
这是一篇译文,读者可前往 The Apache Groovy programming language - Syntax 阅读原文。
本章将讲述 Groovy 语言的语法。Groovy 语言的文法由 Java 语言的文法衍生而来,但同时也通过特定的语法结构对其进行了加强,同时也进行了一定的简化。
1 注释
1.1 单行注释
单行注释由 //
起始,可存在于一行中的任意位置。从 //
开始到行末之间的字符均被视作注释的内容。
1 | // 这是一个独占一行的单行注释 |
1.2 多行注释
多行注释由 /*
起始,可起始于一行中的任意位置。从 /*
开始到第一个遇到的 */
之间的包括换行符在内的所有字符均被视作注释的内容。由此,多行注释可被放在语句的末尾或是语句之间。
1 | /* 一个独占了两行的 |
1.3 GroovyDoc 注释
与多行注释类似,GroovyDoc 注释也可以包括多行,但其由 /**
起始并由 */
终止。从第二行开始,GroovyDoc 注释中的每一行都可以选择以星号 *
起始。这种注释可以与如下几种语言元素相关联:
- 类型定义(类、接口、枚举类型、注解);
- 域和属性的定义;
- 方法定义
尽管即使 GroovyDoc 注释没能和这些语言元素关联在一起编译器也不会有任何反应,但将 GroovyDoc 注释放在这些元素的正上方是更好的做法。
1 | /** |
GroovyDoc 使用了和 Java 的 JavaDoc 相同的语法约定,因此你也可以使用和 JavaDoc 相同的文档注释标签。
1.4 Shebang 行
除了一般的单行注释,还有一种被 Unix 系统称为 Shebang 行(译者注:She 和 Bang 分别对应于 #
和 !
符号) 的注释,它们使得在你安装了 Groovy 并将 groovy
命令放置在 PATH
中后能够从命令行中运行脚本。
1 |
|
#
符号必须为脚本文件的第一个字符,任何缩进都会产生编译错误。
2 关键词
如下表格给出了 Groovy 语言中的所有关键词:
as |
assert |
break |
case |
catch |
class |
const |
continue |
def |
default |
do |
else |
enum |
extends |
false |
finally |
for |
goto |
if |
implements |
import |
in |
instanceof |
interface |
new |
null |
package |
return |
super |
switch |
this |
throw |
throws |
trait |
true |
try |
while |
|
|
|
(译者注:比 Java 多了 def
、in
和 trait
)
3 标识符
3.1 普通标识符
标识符可由字母、美金符号或下滑钱开头,不能以数字开头。
所谓“字母”包括如下范围内的字符:
a
到z
(ASCII 小写字母)A
到Z
(ASCII 大写字母)\u00C0
到\u00D6
\u00D8
到\u00F6
\u00F8
到\u00FF
\u0100
到\uFFFE
标识符接下来的字符可以包括字母和数字。
如下为部分合法标识符:
1 | def name |
如下为部分非法标识符:
1 | def 3tier |
当跟在一个句点(.
)后时,所有关键字均为合法标识符:
1 | foo.as |
3.2 带引号的标识符
带引号的标识符可出现在句点表达式的句点之后。例如,person.name
表达式中的 name
即可被引号包裹,写作 person."name"
或 person.'name'
。如果某些标识符中包含 Java 语言规范不允许但在 Groovy 中被引号包裹时允许存在的字符时,这样的写法就十分有用了。这样的字符包括破折号、空格和感叹号。
1 | def map = [:] |
在后面讲述字符串的章节中我们还能了解到,Groovy 提供了好几种不同的字符串字面量,而所有的这些字符串都可以被放在句点后作为带引号的标识符:
1 | map.'single quote' |
值得注意的是,当使用 Groovy 的 GString(插值字符串)作为带引号的标识符时是和使用普通字符串有所区别的:插值字符串中的值将会被填充,而后再以插值的结果作为标识符进行处理:
1 | def firstname = "Homer" |
4 字符串
文本字面量被表现为名为字符串的一串字符。在 Groovy 中你可以实例化 java.lang.String
对象以及 GString 对象(groovy.lang.GString),而后者在其他某些编程语言中又被称为插值字符串。
4.1 带单引号的字符串
带单引号的字符串(Single Quoted String)为一组由单引号包围的字符:
1 | 'a single quoted string' |
带单引号的字符串实际上即为普通的 java.lang.String
且不支持插值操作。
4.2 字符串拼接
所有 Groovy 字符串可由 +
运算符进行拼接:
1 | assert 'ab' == 'a' + 'b' |
4.3 带三重单引号的字符串
带三重单引号的字符串(Triple Single Quoted String)是一串由三组单引号包围的字符:
1 | '''a triple single quoted string''' |
带三重单引号的字符串实际上即为普通的 java.lang.String
且不支持插值操作。
带三重单引号的字符串可包含多行。你无须将字符串分成若干块并利用字符串拼接或转义的换行符即可使字符串的内容横跨若干行。
1 | def aMultilineString = '''line one |
如果你的代码中包含缩进,例如在类的方法体中时,你的字符串中也会包含缩进所使用的空白字符。GDK 支持使用 String#stripIndent()
方法来移除这些缩进,也可以使用 String#stripMargin()
方法通过给定的分隔符来移除字符串前面的字符。
当你创建如下字符串时:
1 | def startingAndEndingWithANewline = ''' |
你会注意到所创建的字符串的第一个字符为换行符。你可以通过添加一个反斜杠将换行符进行转义以将该换行符从字符串中移除:
1 | def strippedFirstNewline = '''\ |
4.3.1 对特殊字符进行转义
你可以对单引号进行转义以免该单引号中断了字符串字面量:
1 | 'an escaped single quote: \' needs a backslash' |
你也可以通过输入两个连续的反斜杠来对转义符进行转义:
1 | 'an escaped escape character: \\ needs a double backslash' |
某些特殊字符同样使用了反斜杠来作为转义符:
转义序列 | 字符 |
---|---|
'\t' |
制表符 |
'\b' |
退格符 |
'\n' |
换行符 |
'\r' |
回车符 |
'\f' |
进纸符 |
'\\' |
反斜杠 |
'\'' |
单引号(用于带单引号的字符串和带三重单引号的字符串) |
'\"' |
双引号(用于带双引号的字符串和带三重双引号的字符串) |
4.3.2 Unicode 转义序列
要输入那些不存在于你的键盘上的字符,你可以使用 Unicode 转义序列,序列由一个反斜杠,紧接着一个 'u'
再跟着 4 个十六进制数字组成。
例如,欧元货币符号可以这样输入:
1 | 'The Euro currency symbol: \u20AC' |
4.4 带双引号的字符串
带双引号的字符串为一组由双引号包围的字符:
1 | "a double quoted string" |
当不包含插值表达式时,带双引号的字符串将产生一个 java.lang.String
实例,否则将产生一个 groovy.lang.GString
实例。
你可以使用反斜杠对双引号进行转义:
1 | "A double quote: \"" |
4.4.1 字符串插值
除了带单引号的字符串和带三重单引号的字符串,任何 Groovy 表达式都可以被放入到任意的字符串字面量中。插值操作即通过对给定字符串进行处理并用所得值替换字面量中的占位符的操作。占位符表达式可用 ${}
包围,对于句点表达式也可以仅用 $
起始。当 GString
被传递至一个以 String
为参数的方法时,占位符中的表达式将会运算求值,最终调用所得值的 toString()
方法获得其字符串表示。
这里,我们创建了一个字符串,其中包含了一个引用了局部变量的占位符:
1 | def name = 'Guillaume' // a plain string |
但实际上在占位符中使用任何 Groovy 表达式都是合法的。在下面的例子中我们可以看到可以使用代数表达式:
1 | def sum = "The sum of 2 and 3 equals ${2 + 3}" |
实际上除了表达式以外,${}
占位符中同样可以放入语句,但语句的值为 null
,因此如果要在占位符中放入多个语句的话,最后一个语句则应该返回一个比较有意义的用于替换占位符的结果。例如,"The sum of 1 and 2 is equal to ${def a = 1; def b = 2; a + b}"
实际上是可以产生出想要的结果的,但在 GString
占位符中使用简单的表达式依然是更好的做法。
除了 ${}
占位符,我们还可以使用以 $
符号起始的句点表达式:
1 | def person = [name: 'Guillaume', age: 36] |
但只有形如 a.b
或 a.b.c
等的句点表达式可以使用这种写法,如方法调用、闭包等带括号或带代数运算符的表达式是不能这样写的。假设我们定义了如下的数字变量:
1 | def number = 3.14 |
如下语句将会抛出一个 groovy.lang.MissingPropertyException
因为 Groovy 以为你想要访问该变量的 toString
属性,而数字变量本身不包含这样一个属性:
1 | shouldFail(MissingPropertyException) { |
你可能会以为 "$number.toString()"
会被解析器理解为 "${number.toString}()"
。
如果你想要对 GString 中的 $
或 ${}
占位符进行转义使其不要触发插值操作,你只需要使用一个反斜杠符号对 $
符号转义即可:
1 | assert '${name}' == "\${name}" |
4.4.2 使用闭包表达式进行插值
目前来讲,我们了解到我们可以向 ${}
占位符中插入任意的表达式,但如果要插入闭包表达式的话则需要使用一些特殊的符号。当占位符中包含一个箭头符号时,${→}
,该表达式实际上是一个闭包表达式 —— 你可以将其想象成一个前面带着一个美金符号的闭包:
1 | def sParameterLessClosure = "1 + 2 == ${-> 3}" // 注1 |
1 | 该闭包为无参数闭包,无需传入任何实参 |
2 | 该闭包需要传入一个 java.io.StringWriter 参数,你可以使用 << 左移运算符向其中写入内容。在这两个例子中,两个占位符表达式实际上都是闭包。 |
从外表上看,我们似乎只是找到了一种更为繁琐的定义插值表达式的方式,但比起普通的表达式,闭包实际上有一个更大的优势,那就是懒求值。
我们来考虑下面这个案例:
1 | def number = 1 // 注1 |
1 | 这里我们定义了一个值为 1 的变量 number ,而后将其分别作为普通表达式和闭包插入到了 eagerGString 和 lazyGString 中 |
2 | 我们期望 eagerGString 的最终结果包含数值结果 1 |
3 | 同理 |
4 | 而后我们将 number 的值修改 |
5 | 对于普通的插值表达式,插值的结果实际上在创建 GString 的时候就放入其中了 |
6 | 但对于闭包表达式来说,闭包在每次 GString 转换成 String 时都会被调用,如此一来结果字符串中的数字便能随之更新了。 |
如果所使用的闭包表达式包含超过一个的参数的话则会在运行时产生一个异常。只有包含零个或一个参数的闭包可以被用作插值。
4.4.3 Java 插值
如果一个方法(无论是 Java 方法还是 Groovy 方法)需要一个 java.lang.String
作为参数,而我们传入了一个 groovy.lang.GString
对象的话,GString
的 toString()
方法就会被隐式调用。
1 | String takeString(String message) { // 注4 |
1 | 我们创建了一个 GString 变量 |
2 | 这里我们检查一下,确认该对象为 GString 实例 |
3 | 然后我们将该 GString 传入到一个以 String 为参数的方法中 |
4 | takeString() 方法的签名表明了它只接受一个 String 作为参数 |
5 | 我们还确认了实际传入的参数确实是一个 String 而不是 GString |
4.4.4 GString 和 String 的 hashCode
尽管插值字符串可用于代替普通的 Java 字符串,但它们实际上有一点不同:它们的 hashCode
是不同的。普通的 Java 字符串是不可变的,而同一个 GString
的 String
表示则可能发生变化,取决于其被插入的值。除此之外,即使两个对象拥有相同的内容,GString
和 String
的 hashCode
也是不同的:
1 | assert "one: ${1}".hashCode() != "one: 1".hashCode() |
正是由于 GString
和 String
有着不同的 hashCode
,我们不应使用 GString
作为 Map
的键,尤其是当我们需要在后面使用 String
来获取关联的值的时候。
1 | def key = "a" |
1 | 在初始化时,映射中便包含了一对键值对,其中键为一个 GString |
2 | 当我们想要通过一个 String 键获取对应的值时,我们将无法顺利获取,因为 String 和 GString 有着不同的 hashCode 值 |
4.5 带三重双引号的字符串
带三重双引号的字符串和带双引号的字符串类似,只是它们也像带三重单引号的字符串那样,可以包含多行:
1 | def name = 'Groovy' |
在这样的字符串中,双引号和单引号均不需要转义。
4.6 斜杠字符串
除了普通的带引号的字符串,Groovy 还提供了使用 /
作为分隔符的斜杠字符串。斜杠字符串在用来定义正则表达式或正则模式时十分有用,因为在这样的字符串中不需要对反斜杠进行转义:
1 | def fooPattern = /.*foo.*/ |
只有斜杠符本身需要用反斜杠来进行转义:
1 | def escapeSlash = /The character \/ is a forward slash/ |
(译者注:可能无法用这种写法来定义一个以反斜杠结尾的字符串)
斜杠字符串可包含多行:
1 | def multilineSlashy = /one |
斜杠字符串也可以进行插值(也就是说它也是一个 GString
):
1 | def color = 'blue' |
有几点需要注意一下。
当你想定义一个空白的斜杠字符串时,你不能将其写作两个连续的斜杠符,因为 Groovy 解析器会将其认作单行注释。这也是为何如下断言语句无法通过编译,因为编译器认为这个语句不完整:
1 | assert '' == // |
由于斜杠字符串主要是设计来让编写正则表达式变得更加容易,因此一些如 $()
这样的在 GString
中错误的写法实际上是可以被放入到斜杠字符串中的。
4.7 美金斜杠字符串
美金斜杠字符串为使用 $/
起始且使用 /$
结尾的多行 GString
。这样的字符串使用美金符号作为转义符,而且可用于对斜杠或另一个美金符号进行转义。然而在这样的字符串中,斜杠和美金符号都不需要进行转义,除非某个美金符号与后面的字符子串能够组合成一个占位符或者你的字符串中需要包含一个 /$
终止符。
1 | def name = "Guillaume" |
4.8 字符串总结表
字符串种类 | 字符串语法 | 是否支持插值 | 是否可包含多行 | 转义符 |
---|---|---|---|---|
带单引号的字符串 | '...' |
\ |
||
带三重单引号的字符串 | '''...''' |
是 | \ |
|
带双引号的字符串 | "..." |
是 | \ |
|
带三重双引号的字符串 | """...""" |
是 | 是 | \ |
斜杠字符串 | /.../ |
是 | 是 | \ |
美金斜杠字符串 | $/.../$ |
是 | 是 | $ |
4.9 字符
和 Java 不同的是,Groovy 无法显式地创建字符字面量。不过,你可以通过三种不同的方式来将 Groovy 字符串变成字符:
1 | char c1 = 'A' // 注1 |
1 | 将变量显式地声明为 char 类型 |
2 | 使用 as 运算符进行类型转换 |
3 | 强制转换为 char 类型 |
其中,第一种方法适用于将字符赋给一个给定的变量,而其他两种方法则更适用于将字符值作为参数传递给方法。
5 数字
Groovy 支持各种不同种类的整型数或自然数字面量,所有的字面量都将产生 Java 的不同 Number
类型的对象。
5.1 整型数字面量
整型数字面量的类型与 Java 相同:
byte
char
short
int
long
java.lang.BigInteger
通过如下的声明方式即可分别创建上述类型的整型数:
1 | // primitive types |
如果你使用了 def
关键字并不给定类型,整形数的类型则取决于不同类型的容量大小以及具体给定的数值大小。
对于正整数而言:
1 | def a = 1 |
同理,对于负整数而言:
1 | def na = -1 |
5.1.1 其他不以 10 为基的数值表示方法
数字同样可以以二进制、八进制、十六进制或小数形式来表示。
二进制字面量
二进制数字以 0b
起始:
1 | int xInt = 0b10101111 |
八进制字面量
八进制字面量以 0
起始:
1 | int xInt = 077 |
十六进制字面量
十六进制数字以 0x
起始:
1 | int xInt = 0x77 |
5.2 小数字面量
小数字面量的类型与 Java 相同:
float
double
java.lang.BigDecimal
你可以通过如下方式来分别声明上述各个类型的小数:
1 | // primitive types |
小数还可以使用 e
或 E
指数符号并接上一个可选的正负符以及代表指数值整型数值:
1 | assert 1e3 == 1_000.0 |
为了更好地支持精确的小数运算,Groovy 默认使用 java.lang.BigDecimal
作为小数的类型。除此之外,你仍然可以使用 float
或 double
作为小数数值的类型,但这需要你显式的声明变量的类型,或对其进行类型转换或给定特定的后缀。尽管如此,类型为 java.lang.BigDecimal
的字面量仍然可以被用于参数类型为 float
或 double
的闭包和方法。
小数无法使用二进制、八进制或十六进制表示。
5.3 字面量中的下划线
在编写很长的数字字面量时,人眼很难判断如何组合这些数字。Groovy 允许你在数字字面量中放入下划线以更好地区分开不同组的数字:
1 | long creditCardNumber = 1234_5678_9012_3456L |
5.4 数字类型后缀
我们可以通过给定一个大写或小写的后缀使得数字字面量使用我们想要的类型:
类型 | 后缀 |
---|---|
BigInteger |
G 或 g |
Long |
L 或 l |
Integer |
I 或 i |
BigDecimal |
G 或 g |
Double |
D 或 d |
Float |
F 或 f |
1 | assert 42I == new Integer('42') |
5.5 数学运算
尽管我们会在下一章再具体讲述运算符,但我们有必要在这里讲一下各种不同的数学运算的具体行为以及它们的结果类型。
除了除法和乘方运算外:
byte
、char
、short
和int
之间的二元运算结果均为int
类型long
与其他byte
、char
、short
或int
的二元运算结果为long
类型BigInteger
与其他整型类型的二元运算结果为BigInteger
类型BigDecimal
与byte
、char
、short
、int
或BigInteger
的二元运算结果为BigDecimal
类型float
、double
和BigDecimal
之间的二元运算结果均为double
类型- 两个
BigDecimal
进行二元运算的结果为BigDecimal
类型
上述规则可总结为下表:
|
byte |
char |
short |
int |
long |
BigInteger |
float |
double |
BigDecimal |
---|---|---|---|---|---|---|---|---|---|
byte |
int |
int |
int |
int |
long |
BigInteger |
double |
double |
BigDecimal |
char |
|
int |
int |
int |
long |
BigInteger |
double |
double |
BigDecimal |
short |
|
|
int |
int |
long |
BigInteger |
double |
double |
BigDecimal |
int |
|
|
|
int |
long |
BigInteger |
double |
double |
BigDecimal |
long |
|
|
|
|
long |
BigInteger |
double |
double |
BigDecimal |
BigInteger |
|
|
|
|
|
BigInteger |
double |
double |
BigDecimal |
float |
|
|
|
|
|
|
double |
double |
double |
double |
|
|
|
|
|
|
|
double |
double |
BigDecimal |
|
|
|
|
|
|
|
|
BigDecimal |
多亏了 Groovy 的运算符重载功能,你同样可以为 BigInteger
和 BigDecimal
使用算术运算符,而不需要像 Java 那样显式地调用它们的方法。
5.5.1 除法运算符
如果任意一个算子的类型为 float
或 double
,除法运算符 /
(以及 /=
除等运算符)将产生类型为 double
的结果,否则产生类型为 BigDecimal
的结果。
如果除法操作是精确的话(产生一个处于相同精确度和幂的范围内的结果),BigDecimal
的除法由其 divide()
方法实现,否则则使用 MathContext
进行,其中精确度被设定为两个算子的精确度的最高值再额外加 10
,而幂则被设为两个算子的幂和 10
之间的最大值。
对于像 Java 中的那种整型数除法,你应该使用 intdiv()
方法,因为 Groovy 并未提供专门的整型数除法运算符。
5.5.2 乘方运算符
乘方运算符为 **
且包含两个参数:基和指数。乘方运算的结果取决于两个算子以及其本身(尤其是当结果可以被表示为整型数时)。
Groovy 的乘方运算使用如下规则来确定结果的类型:
- 如果指数为小数,
- 如果结果可以被表示为一个
Integer
,则其类型为Integer
- 否则,如果结果可以被表示为一个
Long
,则其类型为Long
- 否则其类型为
Double
- 如果结果可以被表示为一个
- 如果指数为整数,
- 如果指数为负数,那么结果类型为
Integer
、Long
或Double
,取决于结果的数值可以被放入哪个类型中 - 如果指数为零或正数
- 如果基为
BigDecimal
,那么结果类型为BigDecimal
- 如果基为
BigInteger
,那么结果类型为BigInteger
- 如果基为
Integer
且结果可放入到一个Integer
中,那么结果类型为Integer
,否则为BigInteger
- 如果基为
Long
且结果可放入到一个Long
中,那么结果类型为Long
,否则为BigInteger
- 如果基为
- 如果指数为负数,那么结果类型为
如下示例展示了上述的规则:
1 | // base and exponent are ints and the result can be represented by an Integer |
6 布尔类型
布尔类型是一种用于表示真值 true
和 false
的特殊数据类型。这种数据类型应用于记录最简单的真/假条件标识位。
正如其他数据类型,布尔值同样可以被赋值给域或储存在变量中:
1 | def myBooleanVariable = true |
true
和 false
为仅有的两个布尔值,但使用[逻辑运算符]可以写出更为复杂的布尔表达式。
除此之外,Groovy 还有一些被称之为Groovy 真值的特殊规则用于将非布尔类型的对象转换为布尔值。
7 列表
Groovy 使用由逗号分隔且由中括号包围的值来表示列表。由于 Groovy 并未定义自己的集合类,因此 Groovy 的列表实际上就是 JDK 中的 java.util.List
。若无额外显式声明,Groovy 将默认使用 java.util.ArrayList
作为具体的列表实现类。
1 | def numbers = [1, 2, 3] // 注1 |
1 | 我们用几个由逗号分隔并由中括号包围的数字定义了一个列表并将其赋值给了一个变量 |
2 | 该列表为 Java 的 java.util.List 接口的实例 |
3 | 可以使用列表的 `size()` 方法查询其大小,且可见我们的列表中包含 3 个元素 |
上面的例子中创建的列表只包含同类型的元素,但你同样可以创建包含不同类型元素的列表:
1 | def heterogeneous = [1, "a", true] // 注1 |
1 | 列表中包含一个数字、一个字符串和一个布尔值 |
你可以使用下标运算符 []
来访问列表中的元素(可读取或写入),而当所使用的下标值为负数时则可从列表尾部开始访问元素。你还可以使用一个数值范围来获取一个子列表,或使用 <<
左移运算符来向列表追加元素:
1 | def letters = ['a', 'b', 'c', 'd'] |
1 | 访问列表中的第一个元素 |
2 | 使用负索引值访问列表的最后一个元素:-1 代表从列表末尾开始的第一个元素 |
3 | 使用赋值操作将列表的第三个元素重新赋值 |
4 | 使用 << 左移运算符向列表的末尾添加新元素 |
5 | 同时访问两个元素,获取到一个包含这两个元素的列表 |
6 | 使用给定的数值范围访问列表的某个区间之间的数值 |
由于列表可以包含不同类型的元素,列表也可以包含其他列表来构建出一个多维度列表:
1 | def multi = [[0, 1], [2, 3]] // 注1 |
1 | 定义了一个包含数字列表的列表 |
2 | 访问最顶层列表的第二个元素,并访问了该内层列表的第一个元素 |
8 数组
Groovy 使用与列表相同的写法来定义数组,但为了使其确实产生出数组,你需要通过类型转换或显式的类型声明来将其类型定义为数组。
1 | String[] arrStr = ['Ananas', 'Banana', 'Kiwi'] // 注1 |
1 | 使用显式的变量类型定义定义了一个字符串数组 |
2 | 断言我们确实创建了一个字符串数组 |
3 | 使用 as 运算符创建了一个整型数组 |
4 | 断言我们确实创建了一个整型数组 |
你也可以创建多维数组:
1 | def matrix3 = new Integer[3][3] // 注1 |
1 | 你可以定义新数组的大小 |
2 | 或者声明数组但不给定其具体大小 |
访问数组元素的方式与列表相同:
1 | String[] names = ['Cédric', 'Guillaume', 'Jochen', 'Paul'] |
1 | 获取数组的第一个元素 |
2 | 将数组的第三个元素设定为新的值 |
Groovy 不支持 Java 的数组初始化语法,因为大括号会被误解为 Groovy 闭包。
9 映射
尽管在其他语言中又被称为字典或关联数组,Groovy 则支持映射。映射将键与值相互关联,键值使用冒号分隔,而不同的键值对之间使用逗号分隔,最终用中括号包围这些键值对即可定义一个映射。
1 | def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF'] // 注1 |
1 | 我们定义了一组从颜色名称到其十六进制 HTML 颜色代码的映射 |
2 | 我们用下标记号来访问与键 red 相关联的值 |
3 | 我们还可以使用属性访问语法来断言绿色的十六进制表示 |
4 | 同样的,我们还可以使用下标语法来添加一组新的键值对 |
5 | 或者使用属性访问语法来添加颜色 `yellow` |
当使用名称作为键时,实际上我们是在将字符串定义为映射的键。Groovy 创建的映射实为 java.util.LinkedHashMap
的实例。
如果你尝试访问映射中不存在的键:
1 | assert colors.unknown == null |
你所能获得的结果将为 null
。
在上面的例子中,我们使用字符串作为键,但你也可以使用其他类型的值来作为键:
1 | def numbers = [1: 'one', 2: 'two'] |
这里我们使用数字来作为键,那么 Groovy 就不会像之前那样创建字符串来作为键了。但假设你想要将一个变量的值作为键
1 | def key = 'name' |
1 | 与名称 'Guillaume' 相关联的 key 会变成 "key" 字符串,而不是 key 变量的值 |
2 | 映射中不包含 'name' 键 |
3 | 而映射中包含的是 'key' 键 |
当你想将变量的值作为你的定义映射时的键时,你需要将变量或表达式用括号包起来:
1 | person = [(key): 'Guillaume'] // 注1 |
1 | 这次,我们将 key 变量用括号包围,以此告诉解析器我们是在传入一个变量而不是在定义一个字符串键 |
2 | 映射中确实包含键 name |
3 | 且映射中不像之前那样包含键 key |
你还可以将带引号的字符串作为映射的键:["name": "Guillaume"]
。如果你的键字符串不是一个有效的标识符,这么做就是必须的了,比如你想创建一个包含破折号的字符串键:["street-name": "Main street"]
Groovy 语言规范 - 第一章:语法