1. 介绍
本篇为Groovy学习第三篇,接着学习关于Groovy的语法规则知识。
本篇主要学习Groovy中的字符串操作。
了解字符串中的拼接,表达式与字符串的混合拼接。
字符串中的各种转义字符等等。
有很多脚本和开发工具中的插件是使用Groovy进行开发的,了解Groovy可以扩展我们的知识面。
2. Strings 字符串
Groovy中允许两种实例化的字符对象存在,分别为:java.lang.String
和groovy.lang.GString
。
而针对不同的字符串,它支持的多种引号标注。例如单引号:''
和双引号""
2.1 单引号标注
单引号字符串是由单引号包围的一系列字符串。实例效果如:
代码语言:javascript复制def zinyan='这是一个单引号标注的字符串'
通过单引号标注的字符串是纯java.lang.String
对象,不支持插值。
和java中的字符串拼接一样,所有的groovy中的字符串也可以通过
号进行拼接。示例:
assert 'ab' == 'a' 'b'
2.2 三重单引号标注
三重单引号字符串是由单引号的三联体包围的一系列字符。示例如下:
代码语言:javascript复制def zinyan='''这是一个多重单引号标注的字符串'''
通过三重单引号标注的字符串是纯java.lang.String
对象,不支持插值。
和单引号标注的一样。但是相较于单引号标注,三重单引号字符串可以跨越多行。字符串的内容可以跨越行边界,而不需要将字符串分割成几部分,也不需要连接或换行转义字符。示例如下:
代码语言:javascript复制class Zinyan {
def static main(def args) {
def aMultilineString = '''这是第一行zin
这是第二行yan
这是第三行com
'''
println(aMultilineString)
}
}
输出的结果:
代码语言:javascript复制这是第一行zin
这是第二行yan
这是第三行com
我们如果代码中有缩进,例如在类的方法体中,您的字符串将包含缩进的空白。Groovy Development Kit包含了一些方法,它们可以通过String.stripIndent()
方法去掉缩进,还可以通过String.stripMargin()
方法去掉缩进,该方法接受一个分隔符来标识要从字符串开头删除的文本。
def static main(def args) {
// 在 Groovy 中可以使用 Java 语法
def aMultilineString = '''
这是第一行zin
这是第二行yan
这是第三行com
'''
println(aMultilineString)
def stripString=aMultilineString.stripIndent()
println(stripString)
}
输出结果就是:
代码语言:javascript复制> Task :TestLib:Zinyan.main()
这是第一行zin
这是第二行yan
这是第三行com
这是第一行zin
这是第二行yan
这是第三行com
字符串包含一个换行符作为第一个字符。可以通过使用反斜杠转义换行符来删除该字符。 所以在开头的三个单引号后面我添加了一个
否则的话,会多一个换行
2.3 转义字符
和java中的转义字符是一样的。我们如果要输入换行等等,为了避免显示就可以通过实现转义。
例如:
代码语言:javascript复制 def static main(def args) {
def strippedFirstNewline = '这是一个转义单引号: ' 通过斜杠才能显示的(zinyan.com)'
println(strippedFirstNewline)
}
输出的结果为:
代码语言:javascript复制这是一个转义单引号: ' 通过斜杠才能显示的(zinyan.com)
就能够正常的显示单引号了。否则输出的是单引号无法输出。类似的还有符号的输出。
def strippedFirstNewline = '这是一个转义斜杠符号: \ 通过斜杠才能显示的(zinyan.com)'
输出内容为:
代码语言:javascript复制这是一个转义斜杠符号: 通过斜杠才能显示的(zinyan.com)
转义序列 | 字符 |
---|---|
b | 退格 |
f | 跳页 |
n | 换行 |
r | 回车 |
s | 一个空白空间(空格键) |
t | tab按键空间(制表符) |
\ | 一个反斜杠显示 |
' | 一个单引号显示 |
" | 一个双引号显示 |
这里只是列一些基本的,后面学习过程中,将会看到更多的转义细节和字符串。
本质上来说,和其他各种语言中的转义字符定义差不多。如果你对转义字符的相关概念和知识不明白,建议专门了解和学习一下。
在平常使用过程中,转义字符可以说是一个高频使用的功能了。
2.3.1 unicode字符
对于键盘上没有的字符,可以使用unicode转义序列:一个反斜杠,后面跟着'u',然后是4个十六进制数字。
例如人民币符号:
代码语言:javascript复制def strippedFirstNewline ='人民币符号: uFFE5'
println(strippedFirstNewline)
然后我们输出 人民币符号: ¥
人民币的unicode编码为:U FFE5 所以我们可以通过转义字符实现unicode字符码转图案。
更多的unicode字符对应值可以通过 https://unicode-table.com/cn/blocks/ 了解
或者通过浏览器搜索相关的unicode字符表吧。我们比较少用到的罗马字符等 无法通过输入输出时可以通过unicode字符进行编码显示。
2.4 双引号字符
双引号字符串是由双引号包围的一系列字符,实例如下:
代码语言:javascript复制def ztongxue ="这是一个双引号定义的字符串"
如果没有插值表达式,双引号字符串是纯java.lang.String
,但如果有插值,则是groovy.lang.GString
实例。(因为Groovy可以动态确定数据类型。)
任何Groovy表达式都可以插入到所有字符串中,单引号和三单引号字符串除外。插补是在计算字符串时用它的值替换字符串中的占位符的行为。占位符表达式被${}
包围。
对于明确的点式表达式,花括号可以省略,也就是说,在这种情况下,我们可以只使用$
前缀。如果GString被传递给一个接受String的方法,那么占位符中的表达式值将被求值为它的字符串表示形式(通过对该表达式调用toString()),并将得到的String传递给该方法。
通过示例简单理解一下:
代码语言:javascript复制 def static main(def args) {
def name = 'zinyan.com'
def greeting = "Hello ${name}"
println(greeting)
}
输出的内容为:
代码语言:javascript复制Hello zinyan.com
我们还可以通过表达式进行输入:
代码语言:javascript复制 def static main(def args) {
def sum = "2 3的结果是: ${2 3}"
println(sum)
}
输出内容为:
代码语言:javascript复制2 3的结果是: 5
在上面的字符串中,使用了
${}
所以生成的字符串是GString类型。并不是String类型。
不仅允许表达式出现在{}占位符之间,语句也可以。然而,语句的值只是null。因此,如果在占位符中插入了几个语句,最后一个语句应该以某种方式返回要插入的有意义的值。例如,“1和2的和等于{def a = 1;Def b = 2;a b}”是受支持的,而且工作正常,但一个好的实践通常是坚持使用GString占位符中的简单表达式。
除了{}占位符之外,还可以在虚线表达式前面使用单独的符号,示例如下:
代码语言:javascript复制 def static main(def args) {
def person = [name: 'Z同学', age: 3]
def str = "$person.name 的年龄是: $person.age 岁!"
println(str)
}
输出结果为:
代码语言:javascript复制Z同学 的年龄是: 3 岁!
但只有a.b
、a.b.c
等形式的虚线表达式是有效的。
包含括号(如方法调用)、花括号(用于闭包)、不是属性表达式一部分的圆点或算术运算符的表达式将是无效的(例如加减运算符,小数点)。例如:
代码语言:javascript复制 def static main(def args) {
def number = 3.14
def str = "$number.toString()"
}
这种写法就是非法的了。就会在运行的时候出现groovy.lang.MissingPropertyException
异常了。
因为上面的示例,“number.toString()”被解析器解释为“{number.toString}()”
那么我们就是想显示在字符串中拼接上带小数的数字怎么办?很简单,添加{}
就可以了。
示例:
代码语言:javascript复制 def number = 3.14
def str = "${number}"
总而言之,如果表达式输出的结果是模糊的。我们加上花括号就对了。
我们如果要转义字符串中的符号或者{}符号。只需要使用反斜杠字符来转义美元符号就可以了。示例如下:
代码语言:javascript复制class Zinyan {
def static main(def args) {
def str1= "$5"
def str2= "${name}"
println(str1)
println(str2)
}
}
输出的内容为:
代码语言:javascript复制$5
${name}
2.4.1 插值闭包表示的特殊情况
到目前为止,我们已经知道可以在{}占位符中插入任意表达式,但是对于闭包表达式有一种特殊情况和符号。当占位符包含一个箭头{→}时,表达式实际上是一个闭包表达式——你可以把它想象成一个前面加了
代码语言:javascript复制 def static main(def args) {
def sParameterLessClosure = "1 2 == ${-> 3}"
def sOneParamClosure = "1 2 == ${ w -> w << 3}"
println(sParameterLessClosure)
println(sOneParamClosure)
}
输出:
代码语言:javascript复制1 2 == 3
1 2 == 3
闭包是一个不接受参数的无参数闭包。
在这里,闭包接受一个java.io.StringWriter
参数,您可以使用<<
操作符向其追加内容。在任何一种情况下,两个占位符都是嵌入式闭包。
从外观上看,它看起来像是定义要插值的表达式的一种更冗长的方式,但是闭包比单纯的表达式有一个有趣的优势:延迟求值。
示例如下:
代码语言:javascript复制 def static main(def args) {
def number = 1
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"
println(eagerGString)
println(lazyGString)
number = 2
println(eagerGString)
println(lazyGString)
}
输出结果:
代码语言:javascript复制value == 1
value == 1
value == 1
value == 2
可以看到,最后的lazyGString的值进行了变化。这就是闭包表达式的优势了。
对于普通插值表达式,值实际上是在创建GString时绑定的。
但是使用闭包表达式时,每次将GString强制转换为String时都会调用闭包,结果是一个包含新数字值的更新字符串。
请注意:接受多个参数的嵌入式闭包表达式将在运行时生成异常。只允许有零个或一个参数的闭包。
2.4.2 与java的互操性
当一个方法(无论用Java还是Groovy实现)需要java.lang.String
。如果我们传递一个groovy.lang.GString
实例,则GString
的toString()
方法将被自动透明地调用。
也就是说,我们GString和String之间可以互相流畅的转换。
示例如下:
代码语言:javascript复制 def static main(def args) {
//我创建一个GString的对象。
def message = "这个消息是 ${'zinyan.com'}"
def result = takeString(message)
println(result)
}
static String takeString(String message) {
assert message instanceof String
return message
}
在上面的示例中,takeString
要的入参是个String
,而我们给它传的是一个GString
对象。
Groovy会自动帮我们进行转换,调用GString
的toString
方法将字符串转为String
然后传递进去。
2.4.3 字符串的HashCodes
虽然插值字符串可以用来代替纯Java字符串,但它们与字符串有一个特殊的区别:它们的hashcode
是不同的。普通Java字符串是不可变的,而GString的结果String表示可以根据其插入的值而变化。即使对于相同的结果字符串,GStrings和Strings也没有相同的hashCode。
代码语言:javascript复制所以,在Groovy中不能通过hashCode进行比较两个GString和String是否相同。
"one: ${1}".hashCode() != "one: 1".hashCode()
例如上面的这种比较是不对的。
GString和String具有不同的hashCode值,应该避免使用GString作为Map键,特别是当我们试图检索与String而不是GString相关联的值时。例如:
代码语言:javascript复制def key = "a"
def m = ["${key}": "letter ${key}"]
不建议上面的这种写法。因为我们通过 m["a"]
无法获取值,而会返回null
。
2.5 三重双引号标注
和单引号有三重一样。双引号也有三重样式。同时,也是定义多行字符串使用的。
代码语言:javascript复制 def static main(def args) {
def name = 'Zinyan'
def template = """
亲爱的 ${name} 同学:
恭喜你,你的方案被采纳了。
来自Z同学网站消息。
"""
println(template)
}
在三双引号的字符串中,双引号和单引号都不需要转义。我们可以直接使用。
其他的特性就和单引号是一样的。只是双引号可以插值而已。
2.6 斜杠字符串- Slashy String
除了通常的引号字符串,Groovy还提供斜杠字符串,它们使用/
作为开始和结束分隔符。斜杠字符串对于定义正则表达式和模式特别有用,因为不需要转义反斜杠。斜杠字符串的例子:
def static main(def args) {
def fooPattern = /.*zinyan.com.*/
println(fooPattern)
}
输出的内容为:
代码语言:javascript复制.*zinyan.com.*
只有前斜杠需要用反斜杠转义:
代码语言:javascript复制 def escapeSlash = /这是一个正斜杠: / 展示的效果(zinyan.com)/
输出结果为:
代码语言:javascript复制这是一个正斜杠: / 展示的效果
斜杠字符串是通过一对斜杠来确定结束的。所以它默认是支持多行String的。示例:
代码语言:javascript复制def multilineSlashy = /欢迎
访问
zinyan.com/
斜杠字符串也可以被认为是定义GString的另一种方式,但具有不同的转义规则。因此,它们支持插值,我们可以在斜杠定义的字符串中插入变量。示例如下:
代码语言:javascript复制def color = 'blue'
def interpolatedSlashy = /a ${color} car/
2.6.1 特殊情况
空斜杠字符串不能用双正斜杠表示,因为Groovy解析器将其理解为行注释。这就是为什么下面的断言实际上不会编译,因为它看起来像一个非终止语句:
代码语言:javascript复制def color = //
print(color)
所以,我们如果使用斜杠字符串。那么这个字符串必须不能为空。
因为斜杠字符串的设计主要是为了使regexp更容易,所以GString中的一些错误的东西,如()或5将与斜杠字符串一起工作。
记住,转义反斜杠不是必需的。斜杠转义的一个结果是斜杠字符串不能以反斜杠结束。否则将转义斜杠字符串结束符。您可以使用一个特殊的技巧:/这是我们的内容${''},添加有一个反斜杠,最后斜杠结尾/
。但在这种情况下,最好避免使用斜杠字符串。
2.7 美元斜杠字符串
美元斜杠字符串是用开头/和结尾/分隔的多行GString。转义字符是符号,它可以转义另一个或向前斜杠。转义为和斜杠字符仅在与这些字符的特殊使用发生冲突时才需要。字符foo通常表示一个GString占位符,因此这四个字符可以通过转义美元输入到/字符串中,即
示例:
代码语言:javascript复制 def static main(def args) {
def name = "Zinyan.com"
def date = "2022-11-05"
def dollarSlashy = $/
Hello $name,
今天是 ${date}.
$ 这是一个美元符号
$$ 这是一个转义美元符号
这是一个斜杠
/ 这是一个反斜杠
$/ 这是一个转义反斜杠
$$$/ 这是一个转义来的美元符号和反斜杠
$/$$ 这是一个转义了结束语句
/$
println(dollarSlashy)
}
输出效果:
代码语言:javascript复制 Hello Zinyan.com,
今天是 2022-11-05.
$ 这是一个美元符号
$ 这是一个转义美元符号
这是一个斜杠
/ 这是一个反斜杠
/ 这是一个转义反斜杠
$/ 这是一个转义来的美元符号和反斜杠
/$ 这是一个转义了结束语句
创建它是为了克服斜杠字符串转义规则的一些限制。当它的转义规则适合你的字符串内容时使用它。
简单来说,就是如果我们通过反斜杠转义不满足我们的需求的时候,可以试试$ /
反斜杠的模式来转义。
2.8 字符串汇总
字符串名称 | 示例 | 插值 | 多行 | 转义字符 |
---|---|---|---|---|
单引号字符串 | '…' | |||
三重单引号字符串 | '''…''' | √ | ||
双引号字符串 | "…" | √ | ||
三重双引号字符串 | """…""" | √ | √ | |
反斜杠字符串 | /…/ | √ | √ | |
美元反斜杠字符串 | $/…/$ | √ | √ | $ |
2.9 字符-characters
与Java不同,Groovy没有显式的字符文字。在Java中我们通过单引号创建字符Char对象。而在Groovy中默认单引号创建的对象是String。并不是Char对象。但是Groovy也支持Char对象。
可以通过三种不同的方式明确地将Groovy字符串变成实际的字符。示例如下:
代码语言:javascript复制 char c1 = 'A' //通过在声明包含字符的变量时显式地指定字符类型
def c2 = 'B' as char //通过使用as操作符的类型强制转换
def c3 = (char)'C' //通过使用转换到字符的操作
创建的对象都是Char类型。
当字符串保存在变量中使用的时候,可以使用第一种方式更合适。
而必须将Char值作为方法调用的参数进行传递时,使用第二种或第三种方式更合适。
官方说明文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#all-strings
3. 小结
到这里,我们针对Groovy中的String 可以说有一个很大的了解了。
会发现Groovy中的字符串定义和Kotlin和Python中有很多相识的地方。
这就是现在新的高级语言的一些特性了,很多新的高级语言大家的语法有很多相识之处。大家都是互相借鉴的结果。