在编程中,“字符串”是零个或多个字符的序列,您可以将其视为(大部分情况下)一个单一实体。这是一个存在于大多数编程语言中的概念。在 Java 中,因此也在 Groovy 中,字符串主要创建为 String
类的实例。(如果您尚未安装 Groovy,请阅读本简介,了解本系列。)
从 Java 的角度来看,由于 String
是一个类,它(显然)不是原始类型,这最初可能看起来很不幸。但是,当您查看为 String 实例定义的丰富行为时,您可以看到将字符串视为完整的类有很多优势。
例如,有一些方法可以查找 String
实例中的单个字符 (indexOf()
, charAt()
),返回实例的部分内容,可能经过修改 (substring()
, replace()
),应用正则表达式来搜索实例 (matches()
, regionMatches()
) 等等。
顺便说一句,从相同的角度来看,将 Integer
实例而不是 int
,Double
而不是 double
等等也同样有价值。Java 中的 int
只是一个 int
—— 它具有整数值,可能是可变的。它没有其他行为。相比之下,Integer
附带各种有用的行为:max()
和 min()
,parseInt()
,toString()
等等。如前所述,Groovy 将原始类型的所有声明都提升为完整的对象。
在 Java 和 Groovy 中,要认识到
对象的一个重要方面是它们是不可变的,这意味着一旦创建,它们的状态就无法修改。因此,如果您声明了 String
String s = "abc"
,那么就无法将中间的 "b"
转换为另一个字符。表达式 s.replace("b","x")
不会更改 s
;相反,它返回 s
的副本,其中 "b"
被 "x"
替换。(据我从早期的 Java 经验回忆,String 是唯一保持其值不变的类。)
需要可变字符串的程序员应该研究 StringBuffer
类,它除了是可变的之外,还是线程安全的。
现在是向读者解释 Java 关键字 final
含义的好时机。如果原始类型(int
,double
等等)的变量被声明为 final
,则其值不能更改。但是,如果变量是类的实例,则它引用的实例不能更改。但是,如果实例本身是可变的,则可以更改其内容。这是一个小程序,说明了这一点
1 import java.lang.*;
2 public class Groovy09a {
3 public static void main(String[] args) {
4 var t1 = new Test();
5 t1.setA(42);
6 final var t2 = t1;
7 System.out.println("t1.getA() " + t1.getA() + " t2.getA() " + t2.getA());
8 t2.setA(57);
9 System.out.println("t1.getA() " + t1.getA() + " t2.getA() " + t2.getA());
10 }
11 }
12 class Test {
13 private int a = 0;
14 public int getA() { return this.a; }
15 public void setA(int a) { this.a = a; }
16 }
当您运行此程序时,您会看到
$ java Groovy09a
t1.getA() 42 t2.getA() 42
t1.getA() 57 t2.getA() 57
因此,即使 t2
是 final 的,您也可以更改 t2
引用的 Test
实例的字段 a
的值。
但是,如果您插入一行,例如
t2 = new Test();
在第 9 行和第 10 行之间,您将收到以下编译错误
$ javac Groovy09a.java
Groovy09a.java:11: error: cannot assign a value to final variable t2
t2 = new Test();
^
1 error
让我们回到 String
类。
当我编写脚本来处理传入数据时,我经常需要做的事情之一是从每行输入中提取子字符串——或多个子字符串。
在 Java 中,此操作如下所示
line.substring(7,11)
这意味着行的部分从位置 7 开始,到位置 11 之前结束(请记住,字符串以及子字符串,从位置零开始,最后一个位置是字符串长度减 1)。
我可以在 Groovy 中做同样的事情。但是,我也可以使用范围运算符作为从字符串中提取子字符串的更简洁方式
1 String line = "0123456789abcdefghijklmnopqrstuvwxyz"
2 println line.substring(7,11)
3 println line[7..11]
4 println line[7..<11]
5 println line[7..10]
6 println line[30..-1]
7 println line[-6..-1]
当您运行此程序时,您会看到
$ groovy Groovy09b.groovy
789a
789ab
789a
789a
uvwxyz
uvwxyz
在 Groovy 中,符号 a..b
是一个范围,它定义了 a
和 b
之间(包括 a
和 b
)的所有整数。
因此,您看到 line.substring(7,11)
等效于 line[7..<11]
或 line[7..10]
。符号 7…<11 表示从 7 到 11 之前的范围。为了完整起见,符号 6<…<11 表示从 6 之后到 11 之前的范围。了解这种范围也可以应用于数组和列表也很有用。
Groovy 使用附加方法增强了 String
对象的行为。有一组特定的方法,它们的名称都以 “take” 开头,我经常发现它们很有用,特别是对于结构规则但不采用固定格式的文本。例如,有 takeBetween()
方法,允许提取开始标记和结束标记之间的子字符串:结构规则但不采用固定格式。例如,有 takeBetween()
方法,允许提取开始标记和结束标记之间的子字符串
1 String html = """
2 <html>
3 <head>
4 <title>Hello world</title>
5 </head>
6 <body>
7 <h1>Hello world</h1>
8 <p>Hello world</p>
9 </body>
10 </html>
11 """
12 println html.takeBetween("<html>","</html>")
13 println html.takeBetween("<p>","</p>")
当您运行此程序时,您会看到
$ groovy Groovy09c.groovy
<head>
<title>Hello world</title>
</head>
<body>
<h1>Hello world</h1>
<p>Hello world</p>
</body>
Hello world
在这里,您依次提取了 <html>
和 </html>
以及 <p>
和 </p>
之间的文本。
值得一提的是,Groovy 一直允许使用 """
和 '''
序列来开始和结束多行块。
请记住另一个有用的Groovy 对 Java 字符串的补充—— GString
——您在之前的文章中已经看到过。GString
提供了一种将值插入到字符串中的机制。例如,在 Java 中,您会写
System.out.println("t1.getA() " + t1.getA() + " t2.getA() " + t2.getA());
而在 Groovy 中,您可以使用 GString 编写
System.out.println(“t1.getA() ${t1.getA()} t2.getA() ${t2.getA()}”);
或者,当然,您可以进一步缩短它,通过删除 System.out.
,方法调用上的括号,并使用点表示法来访问 getter
println “t1.a ${t1.a} t2.a ${t2.a}”
结论
字符串是编程的基础——在某些方面,编程可以看作是将文本转换为操作,然后再转换回文本。Java 提供了强大的 String
类,可以方便地进行复杂的字符串转换程序。Groovy 为 String
类添加了额外的复杂性,并通过更大的语法支持简化了 String
的使用。正如 Groovy 的学生很快就会意识到的,这种简化既归功于对字符串操作的特定附加语法支持,也归功于 Groovy 中其他很酷的东西的协同作用。这包括范围,范围生成程序员可以利用的涌现属性。
图片由 Daniel Fazio 在 Unsplash 上拍摄
1 条评论