4、字符串
字符以链的形式形成的文本被称作字符串。 Groovy容许你实例化java.lang.String对象,以及在其他语言中被称作插值字符串(interpolated strings)的GStrings (groovy.lang.GString)
4.1、单引号字符串
单引号字符串是一系列被单引号引起来的字符:
def firstname = "Homer"map."Simson-${firstname}" = "Homer Simson"assert map.'Simson-Homer' == "Homer Simson"
单引号字符串是纯的java.lang.String,同时不支持插值。
4.2、字符串连接
所有Groovy的字符串都可以用+号连接起来:
assert 'ab' == 'a' + 'b'
4.3、三引号字符串
三引号字符串是一系列被单引号引起来的字符:
'a single quoted string'
三引号字符串是纯的java.lang.String,同时不支持插值。
三引号字符串可以是多行的。 你不必将字符串分割成几块,也不必用连接符或换行符转义字符来将字符串跨行:
def aMultilineString = '''line one line two line three'''
如果你的代码是缩进的,比如在类里面的方法,你的字符串将会包含这些空白的缩进。 Groovy开发套件提供String#stripIndent()方法来剥离(stripping out)缩进, 同时Groovy开发套件提供String#stripMargin()方法,该方法需要一个分隔符来识别从字符串的开头开始删除的文本。
当创建如下的字符串时:
def startingAndEndingWithANewline = ''' line one line two line three '''
你会发现,得到的是一个以换行符开头的字符串。可以通过反斜杠转义换行来剥离该字符:
def strippedFirstNewline = '''\ line one line two line three '''assert !strippedFirstNewline.startsWith('\n')
4.3.1 转义特殊字符串
你可以用反斜杠来转义单引号来避免终止字符串:
'an escaped single quote: \' needs a backslash'
同时你可以用两个反斜杠转义转义符号本身:
'an escaped escape character: \\ needs a double backslash'
一些特殊的字符串都是用反斜杠来作为转义字符:
转义序列 | 字符 |
---|---|
\t | 制表键 |
\b | 后退键 |
\n | 换行 |
\r | 回车 |
\f | 换页 |
\\ | 反斜杠 |
\' | 单引号 (单引号和三单引号字符串) |
\" | 双引号(双引号和三双引号字符串) |
4.3.2 Unicode转义序列
对于不存在你键盘上的字符,你可以使用Unicode转义序列:反斜杠,其次是u,然后4个十六进制数字。
例如,欧元货币符号可以表示为:
'The Euro currency symbol: \u20AC'
4.4 双引号字符串
双引号字符串是一系列被双引号引起来的字符:
"a double quoted string"
如果没有插值表示式的话,双引号字符串是纯的java.lang.String, 但如果有插值存在的话,双引号字符串就是一个groovy.lang.GString实例。 |
为了转义一个双引号,你可以用反斜杠符号:"A double quote: \"". |
4.4.1 字符串插值
除了单引号和三引号字符串,任何Groovy表达式都可以插值到所有的字符串里。 插值就是当计算字符串的值时,用值替换掉字符串中的占位符。 占位符表达式是用${}围起来的,或者以$为前缀的点表达式。 当一个GString被传到一个需要字符串为参数的方法时,这个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'
不但表达式在${}占位符里面是合法的。语句也同样是合法的,但语句的值为null。 所以如果有多个语句在占位符里面,最后的那个语句应该返回一个有意义的值,以便替换整个占位符的值。 例如, "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()" }
你可以将"$number.toString()"想象为被解析器解释执行为"${number.toString}()". |
如果你想在GString转义$或${}以便不让他们显示为插值, 你只需要用反斜杠转义美元符号:
assert '${name}' == "\${name}"
4.4.2 插值的特殊形式闭包表达式
到目前为止,我们已经看到,我们可以插任意值到${}占位符,但这里有一个特殊情况就是闭包表达式。 当占位符包含一个箭头,${→},这个表达式事实上是一个闭包表达式 — 你可以把它看作是一个带有美元符前缀的闭包:
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参数,你可以用<<左移运算符添加内容。 在那一个情况下,上面两个占位符都是嵌入的闭包。 |
在表面上,这看起来像是一种更加详细定义插值的表达式,但比起其他普通的表达,闭包有一个有趣的优点:延时计算。
一起来考虑下以下的例子:
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的number类型变量,然后我们将它插值到两个GString, eagerGString里使用表达式和lazyGString里使用闭包。 | |
我们期望eagerGString的结果字符串是包含字符串1的。 | |
lazyGString是相类似的方式。 | |
这时我们用一个新的数字修改变量 | |
使用纯插值表达式的话,值实际上是被绑定到创建GString时候的值。 | |
但使用闭包表达式的话,它是每次调用的时候都被强制将从GString转化为String, 结果给包含被更新的新数字值。 |
一个嵌入的闭包表达式接受多于一个参数的时候,将会在运行时产生一个异常。 闭包只接受零个或一个参数。
4.4.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 def result = takeString(message) assert result instanceof Stringassert result == 'The message is hello'
我们创建了一个GString变量 | |
我们复查它的确是一个GString的实例 | |
这时我们传递一个GString给一个接受String作为参数的方法 | |
takeString()方法的签名明确的说明了它的唯一的一个参数是字符串类型 | |
我们也验证参数确实是一个字符串,而不是一个GString。 |
4.4.4 GString和String的hashCode
虽然插值字符串可以被用来替代纯的Java字符串, 但它们有特别的方法来区分字符串:它们的hashcode是不同的。 纯Java字符串是不可变的,而GString表示的结果字符串却是可变的,这取决与它的插值。 即使是相同的结果字符串,GString和String的hasCode也是不同的。
assert "one: ${1}".hashCode() != "one: 1".hashCode()
GString和String有着不同的hasCode值,应该避免把GString用作Map的键,尤其是当我们尝试检索一个值时,应当使用String而不是GString。
def key = "a"def m = ["${key}": "letter ${key}"] assert m["a"] == null
map创建了一个以GString为键的键值对 | |
当我们尝试用String类型键获取值的时候,我们将找不到, 因为String和GString有着不同的hashCode值 |
4.5 三双引号字符串
三双引号字符串表现得像双引号字符串,只是它像三单引号字符串那样,是多行的。
def name = 'Groovy'def template = """ Dear Mr ${name}, You're the winner of the lottery! Yours sincerly, Dave """assert template.toString().contains('Groovy')
在三双引号字符串里的,无论是双引号还是单引号都需要转义的。 |
4.6 斜杠字符串
除了通常的带引号的字符串,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 '' == //
4.7 美元符斜杠字符串
美元符斜杠字符串是以$/开始和以/$结束的多行的GString。 转义的字符是美元符号,美元符号可以转义其他美元符号,或者正斜杠。 但美元符和正斜杠不是一定要去转义, 只要转义那些好像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 dollar slashy string delimiter /$ assert [ 'Guillaume', 'April, 1st', '$ dollar sign', '$ escaped dollar sign', '\\ backslash', '/ forward slash', '$/ escaped forward slash', '/$ escaped dollar slashy string delimiter' ].each { dollarSlashy.contains(it) }
4.8 字符串汇总表
字符串名 | 字符串语法 | 插值 | 多行 | 转义字符 |
单引号 | '…' | 否 | 否 | \ |
三单引号 | '''…''' | 否 | 是 | \ |
双引号 | "…" | 是 | 否 | \ |
三双引号 | """…""" | 是 | 是 | \ |
斜杠 | /…/ | 是 | 是 | \ |
美元符斜杠 | $/…/$ | 是 | 是 | $ |
4.9 字符
与Java不同,Groovy中并没有一个明确的字符文字。 然而你可以有三种不同的方法,明确定义一个Groovy字符串使它实际上就是一字符:
char c1 = 'A' assert c1 instanceof Characterdef c2 = 'B' as char assert c2 instanceof Characterdef c3 = (char)'C' assert c3 instanceof Character
在声明变量的时候,通过显示声明char类型来指定它
使用as操作符来强制转换
使用chat操作符强制转换
当一个变量持有一个字符时,更倾向于第1种选择, 当一个字符值必须传给一个方法调用的时候,更倾向于其他两种(2 and 3)选择。