1.标识符
1.1普通标识符
标识符以字母、美元或下划线开头。它们不能以数字开头。
字母可以介于以下范围内:
- “a” 到 “z”(小写 ascii 字母)
- “A”到“Z”(大写的 ascii 字母)
- “\u00C0”到“\u00D6”
- “\u00D8”到“\u00F6”
- “\u00F8”到“\u00FF”
- “\u0100”到“\uFFFE”
然后,以下字符可以包含字母和数字。
以下是有效标识符的几个示例(此处为变量名称):
def name
def item3
def with_underscore
def $dollarStart
但以下标识符无效:
def 3tier
def a+b
def a#b
当跟随点时,所有关键字也是有效的标识符:
foo.as
foo.assert
foo.break
foo.case
foo.catch
1.2带引号的标识符
带引号的标识符显示在虚线表达式的点之后。例如,person.name
表达式的name
可以用 person.'name'
或 person."name"
来表示。当某些标识符包含 Java 语言规范禁止的非法字符,但 Groovy 在引用这些字符时允许这些字符时,这一点特别有趣。例如,破折号、空格、感叹号等字符,例如:
def map = [:]
map."an identifier with a space and double quotes" = "ALLOWED"
map.'with-dash-signs-and-single-quotes' = "ALLOWED"
assert map."an identifier with a space and double quotes" == "ALLOWED" //true
assert map.'with-dash-signs-and-single-quotes' == "ALLOWED" //true
Groovy提供了不同的字符串文本。实际上,所有类型的字符串都允许在点之后:
map.'single quote'
map."double quote"
map.'''triple single quote'''
map."""triple double quote"""
map./slashy string/
map.$/dollar slashy string/$
普通字符串和Groovy的GStrings(插值字符串)之间存在差异,因为在后一种情况下,内插值插入到最终字符串中以评估整个标识符:
def firstname = "Homer"
map."Simpson-${firstname}" = "Homer Simpson"
assert map.'Simpson-Homer' == "Homer Simpson" //true
2.字符串
文本文本以称为字符串的字符串链的形式表示。Groovy允许您实例化 java.lang.String
对象,以及GStrings(groovy.lang.GString
),这在其他编程语言中也称为插值字符串。
2.1单引号字符串
单引号字符串是一系列用单引号括起来的字符:
'a single-quoted string'
单引号字符串是普通的
java.lang.String
,不支持插值。
2.2字符串连接
所有 Groovy 字符串都可以与运算符+
连接:
assert 'ab' == 'a' + 'b' //true
2.3三重单引号字符串
'''a triple-single-quoted string'''
三重单引号字符串是普通的
java.lang.String
,不支持插值。
三重单引号字符串可能跨越多行。字符串的内容可以跨越行边界,而无需将字符串拆分为多个部分,也无需串联或换行符转义字符:
def aMultilineString = '''line one
line two
line three'''
如果代码缩进(例如在类的方法正文中),则字符串将包含缩进的空格。Groovy 开发工具包包含用于使用String#stripIndent()
方法去除缩进的方法,以及使用采用分隔符字符来标识要从字符串开头删除的文本的String#stripMargin()
方法。
创建字符串时,如下所示:
def startingAndEndingWithANewline = '''
line one
line two
line three
'''
会注意到,生成的字符串包含换行符作为第一个字符。可以通过用反斜杠转义换行符来剥离该字符:
def strippedFirstNewline = '''\
line one
line two
line three
'''
assert !strippedFirstNewline.startsWith('\n') //true
2.3双引号字符串
双引号字符串是一系列用双引号括起来的字符:
"a double-quoted string"
如果没有
java.lang.String
内插表达式,双引号字符串是纯文本的,但如果存在groovy.lang.GString
内插,则为实例。
要转义双引号,可以使用反斜杠字符:“双引号:\”。
2.3.1字符串插值
任何 Groovy 表达式都可以在所有字符串文本中进行插值,单引号和三重单引号字符串除外。插值是在计算字符串时将字符串中的占位符替换为其值的操作。占位符表达式由 ${}
包围。对于明确的虚线表达式,可以省略大括号,即在这些情况下我们可以只使用$
前缀。如果将 GString
传递给采用 String
的方法,则占位符内的表达式值将计算为其字符串表示形式(通过调用toString()
该表达式),并将生成的 String 传递给该方法。
在这里,我们有一个字符串,其中包含一个引用局部变量的占位符:
def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"
assert greeting.toString() == 'Hello Guillaume' //true
任何Groovy表达式都是有效的,正如我们在这个例子中看到的算术表达式:
def sum = "The sum of 2 and 3 equals ${2 + 3}"
assert sum.toString() == 'The sum of 2 and 3 equals 5' //true
不仅允许在
${}
占位符之间使用表达式,而且语句也允许使用。但是,语句的值只是null
。因此,如果在该占位符中插入了多个语句,则最后一个语句应以某种方式返回要插入的有意义的值。例如,“1 和 2 之和等于 ${def a = 1;def b = 2;a + b}“受支持并按预期工作,但一个好的做法通常是坚持使用 GString 占位符内的简单表达式。
除了${}
占位符之外,我们还可以使用一个单独的符号$
作为虚线表达式的前缀:
def person = [name: 'Guillaume', age: 36]
assert "$person.name is $person.age years old" == 'Guillaume is 36 years old' //true
但只有 a.b
、a.b.c
等形式的虚线表达式 才有效。包含括号(如方法调用)、闭包的大括号、不属于属性表达式的点或算术运算符的表达式将无效。给定以下数字的变量定义:
def number = 3.14
以下语句将抛出一个groovy.lang.MissingPropertyException
,因为Groovy认为您正在尝试访问该数字的toString
属性,该属性不存在。
同样,如果表达式不明确,则需要保留大括号:
String thing = 'treasure'
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
"The x-coordinate of the $thing is represented by $thing.x" // <= Not allowed: ambiguous!!
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
"The x-coordinate of the $thing is represented by ${thing}.x" // <= Curly braces required
如果需要转义 GString 中的 ${}
或 $
占位符,以便它们按原样显示而不进行插值,则只需使用反斜杠字符来转义美元符号:
assert '$5' == "\$5"
assert '${name}' == "\${name}"
2.3.2插值闭包表达式的特殊情况
到目前为止,我们已经看到我们可以在${}
占位符内插入任意表达式,但是闭包表达式有一个${→}
表示法。当占位符包含箭头时,表达式实际上是一个闭包表达式 — 您可以将其视为前面有一个美元前缀的闭包:
def sParameterLessClosure = "1 + 2 == ${-> 3}"
assert sParameterLessClosure == '1 + 2 == 3' //true
def sOneParamClosure = "1 + 2 == ${ w -> w << 3}"
assert sOneParamClosure == '1 + 2 == 3' //true
闭包是一个无参数的闭包,它不接受参数
此处,闭包采用单个java.io.StringWriter
参数,您可以使用<<
leftShift 运算符向其追加内容。在任一情况下,两个占位符都是嵌入式闭包。
从外观上看,它看起来像是定义要插值的表达式的一种更冗长的方式,但与单纯表达式相比,闭包具有一个有趣的优势:惰性求值。
让我们考虑以下示例:
def number = 1
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"
assert eagerGString == "value == 1" //true
assert lazyGString == "value == 1" //true
number = 2
assert eagerGString == "value == 1" //true
assert lazyGString == "value == 2" //true
2.3.3与java的互操作性
当一个方法(无论是在Java还是Groovy中实现)需要一个java.lang.String
,但是我们传递一个groovy.lang.GString
实例时,GString的方法会自动透明地调用toString()
。
String takeString(String message) {
assert message instanceof String
return message
}
def message = "The message is ${'hello'}"
assert message instanceof GString //true
def result = takeString(message)
assert result instanceof String //true
assert result == 'The message is hello' //true
- 我们创建一个 GString 变量
- 我们仔细检查它是否是 GString 的一个实例
- 然后,我们将该 GString 传递给一个将 String 作为参数的方法。
-
taskString()
该方法的签名明确表示其唯一参数是 String - 我们还验证该参数确实是字符串而不是 GString。
2.3.4GString & String hashCodes
虽然可以使用内插字符串来代替普通的Java字符串,但它们与字符串在特定方面有所不同:它们的哈希码是不同的。普通 Java 字符串是不可变的,而生成的 GString 字符串表示形式可能会有所不同,具体取决于其内插值。即使对于相同的结果字符串,GStrings 和 Strings 也不具有相同的哈希代码。
assert "one: ${1}".hashCode() != "one: 1".hashCode() //fasle
应该避免使用GString作为Map键的具有不同哈希值的GString和字符串,特别是当我们尝试检索具有字符串而不是GString的关联值时。
def key = "a"
def m = ["${key}": "letter ${key}"]
assert m["a"] == null //true
- 使用初始对创建映射,其键为 GString
- 当我们尝试使用 String 键获取值时,我们将找不到它,因为 Strings 和 GString 具有不同的哈希值
2.4三重双引号字符串
三重双引号字符串的行为类似于双引号字符串,此外,它们是多行的,就像三重单引号字符串一样。
def name = 'Groovy'
def template = """
Dear Mr ${name},
You're the winner of the lottery!
Yours sincerly,
Dave
"""
assert template.toString().contains('Groovy') //true
双引号和单引号都不需要在三重双引号字符串中转义。
2.4.1转义特殊字符
您可以使用反斜杠字符转义单引号,以避免终止字符串文本:
'an escaped single quote: \' needs a backslash'
您可以使用双反斜杠转义转义字符本身:
'an escaped escape character: \\ needs a double backslash'
一些特殊字符还使用反斜杠作为转义字符:
2.4.2统一码转义序列
对于键盘上不存在的字符,可以使用 unicode 转义序列:反斜杠,后跟“u”,然后是 4 个十六进制数字。
例如,欧元货币符号可以用以下方式表示:
'The Euro currency symbol: \u20AC'
2.5斜线
除了通常的带引号的字符串之外,Groovy还提供斜杠字符串,用作开始和结束分隔符。斜杠字符串对于定义正则表达式和模式特别有用,因为不需要转义反斜杠
斜杠字符串的示例:
def fooPattern = /.*foo.*/
assert fooPattern == '.*foo.*' //true
只有正斜杠需要用反斜杠进行转义:
def escapeSlash = /The character \/ is a forward slash/
assert escapeSlash == 'The character / is a forward slash' //true
斜线字符串是多行的:
def multilineSlashy = /one
two
three/
assert multilineSlashy.contains('\n')
斜杠字符串可以被认为是定义GString的另一种方式,但具有不同的转义规则。因此,它们支持插值:
def color = 'blue'
def interpolatedSlashy = /a ${color} car/
assert interpolatedSlashy == 'a blue car' //true
2.5.1特殊情况
空斜杠字符串不能用双正斜杠表示,因为Groovy解析器将其理解为行注释。这就是为什么下面的断言实际上不会编译,因为它看起来像一个非终止语句:
assert '' == // //compilation error
由于斜杠字符串主要是为了让正则表达式更容易而设计的,因此GStrings中的一些错误就像$()
或$5
将适用于斜杠字符串。
请记住,转义反斜杠不是必需的。另一种思考方式是,实际上不支持逃避。斜杠字符串/\t/
将不包含制表符,而是包含后跟字符“t”的反斜杠。仅允许斜杠字符转义,即 /\/folder/
将是一个包含 '/folder'
的斜杠字符串。斜杠转义的结果是斜杠字符串不能以反斜杠结尾。否则,这将转义斜杠字符串终止符。您可以改用一个特殊的技巧./ends with slash ${'\'}/
,但最好避免在这种情况下使用斜杠字符串。
2.6美元斜线
美元斜线字符串是多行GStrings,用$/
开口和/$
结束分隔。转义字符是美元符号,它可以转义另一美元或正斜杠。只有在与特殊使用发生冲突时才需要转义美元和正斜杠字符。这些字符通常表示GString占位符,因此$foo
可以通过转义美元将这四个字符输入到美元斜杠字符串中,即$$foo
.同样,如果您希望一个美元斜杠的收尾分隔符出现在字符串中,则需要对其进行转义。
以下是一些示例:
def name = "Guillaume"
def date = "April, 1st"
def dollarSlashy = $/
Hello $name,
today we're ${date}.
$ dollar sign
$$ escaped dollar sign
\ backslash
/ forward slash
$/ escaped forward slash
$$$/ escaped opening dollar slashy
$/$$ escaped closing dollar slashy
/$
assert [
'Guillaume',
'April, 1st',
'$ dollar sign',
'$ escaped dollar sign',
'\\ backslash',
'/ forward slash',
'/ escaped forward slash',
'$/ escaped opening dollar slashy',
'/$ escaped closing dollar slashy'
].every { dollarSlashy.contains(it) } //all true
它的创建是为了克服斜杠字符串转义规则的一些限制。当它的转义规则适合您的字符串内容时使用它(通常,如果它有一些您不想转义的斜杠)。
2.7字符串汇总表
2.8字符
与Java不同,Groovy没有明确的字符文字。但是,您可以通过三种不同的方式明确地使Groovy字符串成为实际字符:
char c1 = 'A'
assert c1 instanceof Character //true
def c2 = 'B' as char
assert c2 instanceof Character //true
def c3 = (char)'C'
assert c3 instanceof Character //true