1.1.4. 字符串
文本文字用字符串来表示。字符串是字符链的形式。
groovy允许您实例化java.lang.string对象,以及gstring(groovy.lang.gstring),gstring在其他编程语言中也称为内插字符串。
单引号字符串
单引号字符串是由单引号包裹的一系列字符:
'a single-quoted string'
单引号字符串是普通的java.lang.string,不支持插值。
字符串连接
所有groovy字符串都可以用+运算符连接:
assert 'ab' == 'a' + 'b'
三个单引号字符串
三个单引号字符串是由三个单引号包裹的一系列字符:
'''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')
转义特殊字符
可以使用反斜杠字符转义单引号,以避免终止字符串文字:
'an escaped single quote: \' needs a backslash'
您可以用双反斜杠来转义转义符本身:
'an escaped escape character: \\ needs a double backslash'
一些特殊字符还使用反斜杠作为转义符:
Escape sequence | Character |
---|---|
\t | tabulation(制表符) |
\b | backspace(回退符) |
\n | newline(换行符) |
\r | carriage return(回车符) |
\f | formfeed(换页符) |
\\ | backslash(反斜杠) |
\' | single quote within a single-quoted string (and optional for triple-single-quoted and double-quoted strings)(单引号) |
\" | double quote within a double-quoted string (and optional for triple-double-quoted and single-quoted strings)(双引号) |
在后面讨论的其他类型的字符串中,我们将看到更多的转义细节。
Unicode转义序列
对于键盘上不存在的字符,可以使用Unicode转义序列:反斜杠,后跟“U”,然后是4个十六进制数字。
例如,欧元货币符号可以表示为:
'The Euro currency symbol: \u20AC'
双引号字符串
双引号字符串是由双引号包裹的一系列字符:
"a double-quoted string"
如果没有插入表达式,则双引号字符串为plain java.lang.string;如果存在插入,则为groovy.lang.gstring实例。
要转义双引号,可以使用反斜杠字符:“A double quote:\”。
字符串插值
除了单引号字符串和三引号字符串之外,任何Groovy表达式都可以在所有字符串文本中插入。插值是在对字符串进行计算时,用字符串值替换字符串中的占位符的操作。占位符表达式由 包 裹 。 对 于 无 歧 义 的 点 式 表 达 式 , 可 以 省 略 大 括 号 , 也 就 是 说 , 在 这 些 情 况 下 , 我 们 只 能 使 用 {}包裹。对于无歧义的点式表达式,可以省略大括号,也就是说,在这些情况下,我们只能使用 包裹。对于无歧义的点式表达式,可以省略大括号,也就是说,在这些情况下,我们只能使用前缀。如果将gstring传递给接受字符串的方法,则占位符中的表达式值将被计算为其字符串表示形式(通过对该表达式调用ToString()),并将结果字符串传递给该方法。
这里,我们有一个字符串,其中占位符引用了一个局部变量:
def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"
assert greeting.toString() == 'Hello Guillaume'
任何groovy表达式都是有效的,正如我们在本例中通过算术表达式看到的那样:
def sum = "The sum of 2 and 3 equals ${2 + 3}"
assert sum.toString() == 'The sum of 2 and 3 equals 5'
不仅允许在${}占位符之间使用表达式,语句也是如此。但是,语句的值仅为空。因此,如果在该占位符中插入了多个语句,最后一个语句应该以某种方式返回要插入的有意义的值。例如"The sum of 1 and 2 is equal to ${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'
但只有a.b、a.b.c等形式的点式表达式才有效。包含括号的表达式(如方法调用),闭包的大括号,不属于属性表达式或算术运算符的点无效。给定数字的以下变量定义:
def number = 3.14
下面的语句将引发一个groovy.lang.MissingPropertyException,因为groovy认为您正在尝试访问该数字的ToString属性,但该属性不存在:
shouldFail(MissingPropertyException) {
println "$number.toString()"
}
您可以将" n u m b e r . t o S t r i n g ( ) " 视 为 解 析 器 将 其 解 释 为 " number.toString()"视为解析器将其解释为" number.toString()"视为解析器将其解释为"{number.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}"
插值闭包表达式的特例
到目前为止,我们已经看到可以在 占 位 符 内 插 入 任 意 表 达 式 , 但 是 对 于 闭 包 表 达 式 有 一 个 特 殊 的 情 况 和 符 号 。 当 占 位 符 包 含 一 个 箭 头 , {}占位符内插入任意表达式,但是对于闭包表达式有一个特殊的情况和符号。当占位符包含一个箭头, 占位符内插入任意表达式,但是对于闭包表达式有一个特殊的情况和符号。当占位符包含一个箭头,{→}时,表达式实际上是一个闭包表达式——可以将其视为一个在其前面有$的闭包:
def sParameterLessClosure = "1 + 2 == ${-> 3}"
assert sParameterLessClosure == '1 + 2 == 3' //①
def sOneParamClosure = "1 + 2 == ${ w -> w << 3}"
assert sOneParamClosure == '1 + 2 == 3' //②
①这个闭包是不带参数的无参数闭包。
②这里,闭包只需要一个java.io.StringWriter参数,您可以使用<<leftshift运算符将内容追加到该参数中。在这两种情况下,两个占位符都是嵌入的闭包。
在外观上,它看起来像是定义要插入的表达式的一种更详细的方法,但是闭包比单纯的表达式有一个有趣的优势:懒惰的计算。
让我们来看看 下面的例子:
def number = 1 //①
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"
assert eagerGString == "value == 1" //②
assert lazyGString == "value == 1"
//③
number = 2 //④
assert eagerGString == "value == 1" //⑤
assert lazyGString == "value == 2"
//⑥
① 我们定义了一个包含1的数字变量,然后在两个gstring中进行内插,分别是eagerGString中的表达式和lazyGString中的闭包。
②我们期望得到的字符串包含与eagerGString相同的字符串值1。
③lazyGString也一样。
④然后我们把变量的值改成一个新的数字。
⑤普通的插值表达式,它的值实际上是在创建Gstring的时候绑定的。
⑥但是对于一个闭包的表达式来说,每次强制gstring变为字符串时都会调用闭包,从而生成包含新数值的更新过的字符串。
使用多个参数的嵌入式闭包表达式将在运行时抛出异常。闭包表达式中只允许0个或者1个参数。
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 //②
def result = takeString(message) //③
assert result instanceof String
assert result == 'The message is hello'
①我们创建一个GString变量
②我们仔细检查一下这是GString的一个实例
③然后我们把GString传入一个需要String作为参数的方法中
④takeString()方法的签名显式地表示其唯一参数是字符串
⑤我们还验证了参数确实是String,而不是GString。
GString 和 String 的哈希码
虽然插值字符串可以代替普通的Java字符串,但它们在特殊情况下是不同的:它们的哈希码是不同的。普通Java字符串是不可变的,而GString的结果字符串表示可以不同,这取决于其内插值。即使对于相同的结果字符串,GString和String也没有相同的哈希代码。
assert "one: ${1}".hashCode() != "one: 1".hashCode()
GString 和 Strings 有不同的哈希值,使用GString作为Map的key这是应该被避免的,尤其是当我们使用String替代GString作为查询相关值的key时。
def key = "a"
def m = ["${key}": "letter ${key}"] //①
assert m["a"] == null //②
①使用初始的对来创建map,GString作为这个map的key
②当我们使用String类型的key来检索这个值时,我们找不到,这个是因为String和GString有不同的哈希码。
三重双引号字符串
三重双引号字符串的用法类似于双引号字符串,加上它们是多行的,就像三重单引号字符串一样。
def name = 'Groovy'
def template = """
Dear Mr ${name},
You're the winner of the lottery!
Yours sincerly,
Dave
"""
assert template.toString().contains('Groovy')
双引号和单引号都不需要在三重双引号字符串中转义。
斜杠字符串
除了常用的带引号的字符串之外,groovy还提供斜杠字符串,这些字符串使用/作为开始和结束分隔符。斜杠字符串对于定义正则表达式和模式特别有用,因为不需要转义反斜杠。
一个斜杠字符串的例子:
def fooPattern = /.*foo.*/
assert fooPattern == '.*foo.*'
只有正斜杠需要用反斜杠转义:
def escapeSlash = /The character \/ is a forward slash/
assert escapeSlash == 'The character / is a forward slash'
斜杆字符串可以是多行的:
def multilineSlashy = /one
two
three/
assert multilineSlashy.contains('\n')
斜杆字符串可以被认为是定义GString的另一种方法,但有不同的转义规则。因此,它们支持插值。
def color = 'blue'
def interpolatedSlashy = /a ${color} car/
assert interpolatedSlashy == 'a blue car'
特殊情况
空斜杠字符串不能用双正斜杠表示,因为groovy解析器将其理解为行注释。这就是为什么下面的断言实际上不会像非终止语句那样编译:
assert '' == //
因为斜杠字符串主要是为了使regexp更容易,所以GString中的一些错误(如$()或$5)可以与斜杠字符串一起使用。
记住,转义反斜杠是不需要的。换句话说,转义反斜杠是不需要的。斜杠字符串/\t/不包含制表符,而是反斜杠后面的字符‘t’,只有斜杠字符才允许转义,即//folder/将是’/folder’的斜杠字符串。斜杠转义的结果是斜杠字符串不能以反斜杠结尾。否则,将退出斜杠字符串终止符。可以使用一个特殊的技巧,/ends with slash ${’’}/。但最好避免在这种情况下使用斜线。
美元斜杠字符串
美元斜杠字符串是一个多行的字符串,以 / 作 为 开 头 , / /作为开头,/ /作为开头,/作为结尾,这样来分隔整个字符串。转义字符是美元符号,它可以转义另一个美元或正斜杠。但是美元和正斜杠都不需要转义,除非转义像gstring占位符序列一样开始的字符串子序列的美元,或者如果需要转义像结束美元斜杠字符串分隔符一样开始的序列。
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) }
它是为了克服斜杠字符串转义规则的一些限制而创建的。当它的转义规则适合您的字符串内容时使用它(通常如果它有一些您不想转义的斜线)。
字符串汇总表
String name | String syntax | Interpolated | Multiline | Escape character |
Single-quoted |
|
| ||
Triple-single-quoted |
| √ |
| |
Double-quoted |
| √ |
| |
Triple-double-quoted |
| √ | √ |
|
Slashy |
| √ | √ |
|
Dollar slashy |
| √ | √ |
|
字符
与Java不同,Groovy没有显式字符文字。但是,您可以通过三种不同的方式明确地将groovy字符串设置为实际字符:
char c1 = 'A' //①
assert c1 instanceof Character
def c2 = 'B' as char //②
assert c2 instanceof Character
def c3 = (char)'C' //③
assert c3 instanceof Character
①通过指定char类型声明包含字符的变量时显式
②通过将类型强制与as运算符一起使用
③通过使用强制转换为字符的操作
当字符保存在变量中时,第一个选项①很适用,而当必须将char值作为方法调用的参数传递时,其他两个选项(②和③)更适用。