Groovy语言规范--字符串篇(二)

本文深入探讨Groovy的字符串类型,包括单引号和三引号字符串、字符串连接、Unicode转义序列、双引号字符串与字符串插值。此外,还讲解了与Java的互操作性、GString和String的哈希码差异,以及特殊类型的字符串如三重双引号字符串和斜杠字符串等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 sequenceCharacter

\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值作为方法调用的参数传递时,其他两个选项(②和③)更适用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值