Groovy 字符串

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
  1. 在声明变量的时候,通过显示声明char类型来指定它

  2. 使用as操作符来强制转换

  3. 使用chat操作符强制转换

当一个变量持有一个字符时,更倾向于第1种选择, 当一个字符值必须传给一个方法调用的时候,更倾向于其他两种(2 and 3)选择。