Groovy语法系列教程之字符串(三)


Groovy语法概述

本系列教程介绍Groovy编程语言的语法。Groovy的语法源自Java语法,但是通过特定类型对其进行了增强,并允许进行某些简化。

4. 字符串

文本文字以多个字符串联的形式表示,称为字符串。

Groovy允许您实例化java.lang.String对象以及GString(groovy.lang.GString),后者在其他编程语言中也称为内插字符串。

4.1 单引号字符串

单引号字符串是一系列用单引号引起来的字符:

'我的博客:https://tangming.org/'

单引号字符串是纯粹的java.lang.String,不支持插值。

4.2 字符串连接

所有的Groovy字符串都可以用+运算符连接:

assert 'ab' == 'a' + 'b'

4.3 三重单引号字符串

三重单引号字符串是一系列字符,由3个单引号包围:

'''一个三重单引号字符串'''

三重单引号字符串也是纯粹的java.lang.String,不支持插值。

三重单引号字符串可能跨越多行。

字符串的内容可以跨越行边界,而无需将字符串分成几段,也不需要连接或换行符:

def aMultilineString = '''第一行
第二行
第三行'''

如果您的代码是缩进的,例如在类的方法体中,则您的字符串将包含缩进的空格。

Groovy开发工具包包含去除缩进的方法String#stripIndent()和使用分隔符标识从字符串开头删除文本的方法String#stripMargin()

当按如下方式创建字符串:

def startingAndEndingWithANewline = '''
第一行
第二行
第三行'''

您会注意到,结果字符串的第一个字符是换行符。可以通过用反斜杠转义换行符来删除该字符:

def strippedFirstNewline = '''\
第一行
第二行
第三行'''

assert !strippedFirstNewline.startsWith('\n')

4.3.1 转义特殊字符

您可以使用反斜杠字符\对单引号进行转义,以避免终止字符串文字:

'转义的单引号:\'需要反斜杠'

您可以使用双反斜杠来转义转义字符:

'一个转义的转义字符:\\需要双反斜杠'

一些特殊字符还将反斜杠用作转义字符:

转义字符 意义
\t 制表符
\b 退格
\n 换行
\r 回车
\f 换页
\\ 反斜杠
' 单引号字符串中的单引号(对于三重单引号和双引号字符串是可选的)
" 双引号字符串中的双引号(对于三重双引号和单引号字符串是可选的)
\t 制表符
\b 退格键

在稍后介绍的其他类型的字符串中,我们将看到更多关于转义的知识。

4.3.2 Unicode转义序列

对于键盘上不存在的字符,可以使用Unicode转义序列:一个反斜杠,后跟’u’,然后是4个十六进制数字。

例如,欧元货币符号可以表示为:

'欧元符号:\u20AC'

4.4 双引号字符串

双引号字符串是一系列用双引号引起来的字符:

"双引号字符串"

如果没有插值表达式,则双引号字符串为纯java.lang.String;如果存在插值,则为groovy.lang.GString实例。

要转义双引号,可以使用反斜杠字符:双引号:\""

4.4.1 字符串插值

Groovy表达式可以插入所有类型字符串中,除了单引号和三重单引号字符串。

插值是在评估字符串时用其值替换字符串中占位符的行为。 占位符表达式由${}包围。

对于明确的点分表达式,可以省略花括号,即在这种情况下,我们可以只使用$前缀。

如果将GString传递给采用String的方法,则占位符内的表达式值将求值为其字符串表示形式(通过在该表达式上调用toString()),并将生成的String传递给该方法。

下面是一个带有占位符的字符串,该字符串引用一个局部变量:

def url = 'https://tangming.org' // a plain string
def greeting = "欢迎访问${url}"

assert greeting.toString() == '欢迎访问https://tangming.org'

任何Groovy表达式都是有效的,如使用算术表达式:

def sum = "2加3等于${2 + 3}"
assert sum.toString() == '2加3等于5'

${}占位符之间不仅允许使用表达式,也允许使用语句。 但是,语句的值是null

因此,如果在该占位符中插入了多个语句,则最后一个应以某种方式返回要插入的有意义的值。 例如,"1和2的总和等于$ {def a = 1; def b = 2; a + b}"受支持并按预期工作,但是一种好的做法通常是在GString占位符内部坚持简单的表达式 。

除了${}占位符,我们还可以在点分表达式前加上一个单独的$符号:

def blog = [url: 'https://tangming.org', author: hummerstudio]
assert "Url of $blog.author's blog is $blog.url" == "Url of hummerstudio's blog is https://tangming.org"

但是只有a.ba.b.c等形式的点分表达式有效。

包含圆括号的表达式(例如方法调用,用于闭包的大括号,不属于属性表达式或算术运算符的点)将无效。

给定以下数字变量定义:

def number = 3.14

以下语句将抛出异常groovy.lang.MissingPropertyException,因为Groovy认为您正在尝试访问该数字的toString属性,但该属性并不存在:

shouldFail(MissingPropertyException) {
    println "$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"   // <= 不允许:有歧义
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
        "The x-coordinate of the $thing is represented by ${thing}.x"  // <= 需要花括号

如果您需要在GString中转义$${}占位符,以便它们按原样显示而无需插值,则只需要使用`反斜杠字符即可转义美元符号:

assert '$5' == "\$5"
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行。我们定义一个等于1number变量,然后在两个GString中插值,作为eagerGString中的一个表达式和lazyGString中的一个闭包
  • 第4行。我们期望结果字符串包含与eagerGString相同的字符串值1
  • 第5行。类似的lazyGString
  • 第6行。然后我们将变量的值更改为新的数字
  • 第7行。使用普通的插值表达式,该值实际上是在创建GString时绑定的。

  • 第8行。但是使用闭包表达式时,每次将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 String
assert result == 'The message is hello'
  • 我们创建一个GString变量
  • 我们再次检查它是GString的实例
  • 然后,我们将该GString传递给采用String作为参数的方法
  • takeString()方法的声明明确表示其唯一的参数是String
  • 我们还验证该参数确实是String而不是GString

4.4.4 GString和String的哈希码

尽管可以使用内插的字符串代替普通的Java字符串,但是它们与字符串不同:它们的哈希码不同。

纯Java字符串是不可变的,而由GString生成的String表示形式可能有所不同,具体取决于其内插值。

即使对于相同结果第字符串,GString和String的哈希码也是不同的。

assert "one: ${1}".hashCode() != "one: 1".hashCode()

GString和String具有不同的哈希值,应避免使用GString作为Map的键,尤其是当我们尝试使用String而不是GString来取关联值时。

def key = "a"
def m = ["${key}": "letter ${key}"]     

assert m["a"] == null   

使用GString作为键,当我们尝试使用String键获取值时,我们将找不到它,因为String和GString具有不同的哈希值。

4.5 三重双引号字符串

三重双引号字符串的行为类似于双引号字符串,但它们可以是多行的,与三重单引号字符串类似。

def name = '同学们'
def template = """
    Dear ${name},

    你好!

    学习Groovy、Jenkins
    
    欢迎访问https://tangming.org
"""

assert template.toString().contains('同学们')

双引号或单引号都不需要在三重双引号字符串中进行转义。

4.6 斜线字符串

除了通常引用的字符串外,Groovy还提供了斜线字符串,它们使用/作为开始和结束定界符。

斜线字符串对于定义正则表达式和模式特别有用,因为不需要转义反斜杠。

斜线字符串示例:

def fooPattern = /.*foo.*/
assert fooPattern == '.*foo.*'

仅需使用正斜杠将反斜杠转义:

def escapeSlash = /字符 \/ 是正斜杠/
assert escapeSlash == '字符 / 是正斜杠'

斜线字符串为多行:

def multilineSlashy = /第一行
    第二行
    第三行/

assert multilineSlashy.contains('\n')

斜线字符串可以被认为是定义GString的另一种方式,但是具有不同的转义规则。 因此,它们支持插值:

def color = 'blue'
def interpolatedSlashy = /a ${color} car/

assert interpolatedSlashy == 'a blue car'

4.6.1 特殊情况

空的斜杠字符串不能用双斜杠表示,因为Groovy解析器将其理解为行注释。

这就是为什么以下断言实际上不会编译的原因,因为它看起来像是一个未完成的语句:

assert '' == //

由于斜线字符串的主要目的是使正则表达式更容易使用,因此像$()$5等在GString中会出错的内容,在斜线字符串中使用是可以的。

请记住,不需要转义反斜杠。或者可以理解为实际上不支持转义。斜线字符串/\t/将不包含制表符,而是包含反斜杠,后跟字符“t”。

仅允许对斜杠字符进行转义,即/\/folder/将是包含'/folder'的斜杠字符串。

斜杠转义的很重要的一点是,斜线字符串不能以反斜杠结尾。否则,它将转义斜线字符串终止符。

您可以改用一个特殊技巧,/ends with slash ${'\'}/。但最好避免在这种情况下使用斜线字符串。

4.7 美元斜线字符串

美元斜线字符串是多行GString,以$/开始/$结束。

转义字符是美元符号,它可以转义另一个美元或正斜杠。

美元和正斜杠都不需要转义,除了/$$/

示例:

def name = "Guillaume"
def date = "April, 1st"

def dollarSlashy = $/
    Hello $name,
    today we're ${date}.

    $ 美元符号
    $$ 转义美元符号
    \ 反斜杠
    / 正斜杠
    $/ 转义正斜杠
    $$$/ 转义$/
    $/$$ 转义/$
/$

assert [
    'Guillaume',
    'April, 1st',
    '$ 美元符号',
    '$ 转义美元符号',
    '\\ 反斜杠',
    '/ 正斜杠',
    '/ 转义正斜杠',
    '$/ 转义$/',
    '/$ 转义/$'
].every { dollarSlashy.contains(it) }

它的创建是为了克服斜线转义规则的某些限制。

当其转义规则适合你的字符串内容时使用(通常是有一些你不想转义的斜杠时)。

4.8 各类字符串汇总表

字符串类型 字符串语法 可插值 可多行 转义字符
单引号 '...' \
三重单引号 '''...''' \
双引号 "..." \
三重双引号 """"...""" \
斜线 /.../ \
美元斜线 $/.../$ $

4.9 字符

与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操作

当要将字符保留在变量中时,使用第一种方法;而当必须将char类型值作为方法调用的参数传递时,可使用其他两种方法。


 关注微信公众号

DevOps持续交付公众号ID:devopscd