1. 概述

在本教程中,我们将深入探讨 Groovy 中的多种字符串类型,包括单引号、双引号、三引号以及斜杠字符串(slashy strings)。

我们还会涉及 Groovy 字符串对特殊字符、多行文本、正则表达式、转义和变量插值的支持。

2. 对 java.lang.String 的增强

Groovy 是基于 Java 的语言,因此它天然具备 Java 中所有 String 的能力,比如字符串拼接、丰富的 API 接口,以及字符串常量池带来的性能优势。

不过,Groovy 在这些基础之上进行了扩展,提供了更多灵活的语法支持。

2.1. 字符串拼接

字符串拼接是将两个字符串连接在一起的操作:

def first = 'first'
def second = "second"        
def concatenation = first + second
assertEquals('firstsecond', concatenation)

Groovy 支持不同类型的字符串之间进行拼接操作 ✅。

2.2. 字符串插值

Java 只能通过 printf 实现基本的模板功能,而 Groovy 提供了更强大的 字符串插值 功能,允许在字符串中嵌入变量:

def name = "Kacper"
def result = "Hello ${name}!"
assertEquals("Hello Kacper!", result.toString())

⚠️ 虽然各种字符串都支持拼接,但只有特定类型的字符串才支持插值。

2.3. GString

上面的例子中有个小细节需要注意:为什么我们要调用 .toString()

其实,result 并不是 String 类型,而是 Groovy 自己实现的一个类 —— **GString**。

由于 Java 的 Stringfinal 类型,Groovy 无法继承它来添加插值功能。因此,Groovy 使用了自己的字符串类型 GString 来实现插值。

例如:

assertEquals("Hello Kacper!", result)

这会触发 assertEquals(Object, Object) 方法,并抛出异常:

java.lang.AssertionError: expected: java.lang.String<Hello Kacper!>
  but was: org.codehaus.groovy.runtime.GStringImpl<Hello Kacper!>
Expected :java.lang.String<Hello Kacper!> 
Actual   :org.codehaus.groovy.runtime.GStringImpl<Hello Kacper!>

3. 单引号字符串

单引号字符串是最简单的字符串类型:

def example = 'Hello world'

它们本质上就是 Java 的 String,适用于需要包含引号的场景,避免使用转义字符:

def easyToRead = 'Kacper loves "Lord of the Rings"'

4. 三重单引号字符串

三重单引号字符串非常适合表示多行内容,比如 JSON 或 SQL:

def jsonContent = '''
{
    "name": "John",
    "age": 20,
    "birthDate": null
}
'''

4.1. 新行处理

默认情况下,三重单引号字符串会保留首尾的新行符:

def triple = '''
    firstline
    secondline
'''

实际内容为:

(newline)
    firstline(newline)
    secondline(newline)

可以通过在首尾加上反斜杠 \ 来去掉首行的换行:

def triple = '''\
    firstline
    secondline
'''

4.2. 去除缩进

为了保持代码整洁又不影响字符串内容,可以使用 stripIndent() 方法:

def triple = '''\
    firstline
    secondline'''.stripIndent()
assertEquals("firstline\nsecondline", triple)

4.3. 相对缩进

stripIndent() 会根据最短非空行的缩进作为基准:

def triple = '''\
            firstline
                secondline\
        '''.stripIndent()

输出结果为:

firstline
    secondline

4.4. 使用 stripMargin()

还可以使用 |stripMargin() 来控制起始位置:

def triple = '''\
    |firstline
    |secondline'''.stripMargin()

输出:

firstline
secondline

4.5. 转义特殊字符

虽然三重单引号字符串不需要转义双引号,但仍需转义单引号和反斜杠:

常见需要转义的字符包括:

  • \t – 制表符
  • \n – 换行符
  • \b – 退格
  • \r – 回车
  • \\ – 反斜杠
  • \f – 换页
  • \' – 单引号

示例:

def specialCharacters = '''hello \'John\'. This is backslash - \\ \nSecond line starts here'''

5. 双引号字符串

双引号字符串同样也是 Java 的 String,但它支持插值功能。

5.1. GString 与延迟求值

当双引号字符串中含有插值时,Groovy 会自动将其转换为 GString

def 'String ang GString'() {
    given:
    def string = "example"
    def stringWithExpression = "example${2}"

    expect:
    string instanceof String
    stringWithExpression instanceof GString
    stringWithExpression.toString() instanceof String
}

5.2. 插值变量引用

最常用的插值方式是引用变量:

def 'placeholder with variable'() {
    given:
    def name = "John"

    when:
    def helloName = "Hello $name!".toString()

    then:
    helloName == "Hello John!"
}

5.3. 插值表达式

也可以插入表达式:

def 'placeholder with expression'() {
    given:
    def result = "result is ${2 * 2}".toString()

    expect:
    result == "result is 4"
}

⚠️ 不建议在插值中写复杂语句。

5.4. 对象属性访问

可以通过点号访问对象属性:

def 'placeholder with dotted access'() {
    given:
    def person = [name: 'John']

    when:
    def myNameIs = "I'm $person.name, and you?".toString()

    then:
    myNameIs == "I'm John, and you?"
}

调用方法时必须使用 ${}

def 'placeholder with method call'() {
    given:
    def name = 'John'

    when:
    def result = "Uppercase name: ${name.toUpperCase()}".toString()

    then:
    result == "Uppercase name: JOHN"
}

5.5. GString 与 String 的 hashCode 差异

⚠️ GStringStringhashCode 不一致:

def 'GString and String hashcode'() {
    given:
    def string = "2+2 is 4"
    def gstring = "2+2 is ${4}"

    expect:
    string.hashCode() != gstring.hashCode()
}

❌ 所以不要将 GString 作为 Map 的 key!

6. 三重双引号字符串

结合双引号和三引号的优点,支持多行插值:

def name = "John"
def multiLine = """
    I'm $name.
    "This is quotation from 'War and Peace'"
"""

✅ 不需要转义单引号或双引号。

7. 斜杠字符串(Slashy String)

斜杠字符串主要用于正则表达式,无需转义反斜杠:

def pattern = /\d{3}\s\w+\s\w+\\\w+/
assertTrue("3 Blind Mice\Men".matches(pattern))

✅ 支持插值和多行:

def name = 'John'
def example = /
    Dear ([A-Z]+),
    Love, $name
/

⚠️ 需要转义斜杠 /

def pattern = /.*foobar.*\/hello.*/

❌ 不能表示空字符串,因为 // 会被当作注释。

8. 美元斜杠字符串(Dollar-Slashy String)

用于避免频繁转义斜杠 /,以 $/$ 开始和结束:

def name = "John"

def dollarSlashy = $/
    Hello $name!,

    I can show you a $ sign or an escaped dollar sign: $$ 
    Both slashes work: \ or /, but we can still escape it: $/
            
    We have to escape opening and closing delimiters:
    - $$$/  
    - $/$$
 /$

输出:

Hello John!,

I can show you a $ sign or an escaped dollar sign: $ 
Both slashes work: \ or /, but we can still escape it: /

We have to escape opening and closing delimiter:
- $/  
- /$

9. 字符类型(Character)

Groovy 没有显式的字符字面量,单引号默认是字符串。

要创建字符,有三种方式:

  • 显式声明为 char
  • 使用 as 运算符
  • 强制类型转换为 char
def 'character'() {
    given:
    char a = 'A' as char
    char b = 'B' as char
    char c = (char) 'C'

    expect:
    a instanceof Character
    b instanceof Character
    c instanceof Character
}

10. 总结

快速回顾一下重点:

  • 单引号字符串不支持插值 ❌
  • 斜杠和三重双引号支持多行 ✅
  • 多行字符串包含空白字符,需手动清理 ⚠️
  • 除美元斜杠字符串外,其他字符串都使用 \ 转义 ✅

11. 结论

本文介绍了 Groovy 中的多种字符串类型及其特性,包括多行支持、插值、正则表达式等。如需了解更多 Groovy 特性,请参考我们的 Groovy 语言入门指南


原始标题:Types of Strings in Groovy | Baeldung