Skip to content

Commit bd044b3

Browse files
committed
Translate 2 items of Strings
1 parent 320e8e5 commit bd044b3

1 file changed

Lines changed: 88 additions & 6 deletions

File tree

docs/book/18-Strings.md

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,98 @@
22

33
<!-- -->
44
# 第十八章 字符串
5-
6-
5+
可以证明,字符串操作是计算机程序设计中最常见的行为之一。
6+
在Java大展拳脚的Web系统中更是如此。在本章中,我们将深入学习在Java语言中应用最广泛的**String**类,并研究与之相关的类及工具。
77
<!-- Immutable Strings -->
88
## 字符串的不可变
9-
10-
9+
**String**对象是不可变的。查看JDK文档你就会发现,**String**类中每一个看起来会修改**String**值的方法,实际上都是创建了一个全新的**String**对象,以包含修改后的字符串内容。而最初的**String**对象则丝毫未动。
10+
11+
看看下面的代码:
12+
```java
13+
// strings/Immutable.java
14+
public class Immutable {
15+
public static String upcase(String s) {
16+
return s.toUpperCase();
17+
}
18+
public static void main(String[] args) {
19+
String q = "howdy"; System.out.println(q); // howdy
20+
String qq = upcase(q);
21+
System.out.println(qq); // HOWDY
22+
System.out.println(q); // howdy
23+
}
24+
}
25+
/* Output:
26+
howdy
27+
HOWDY
28+
howdy
29+
*/
30+
```
31+
当把q传递给upcase()方法时,实际传递的是引用的一个拷贝。其实,每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指向的对象其实一直待在单一的物理位置上,从未动过。
32+
33+
回到upcase()的定义,传入其中的引用有了名字s,只有upcase()运行的时候,局部引用s才存在。一旦upcase()运行结束,s就消失了。当然了,upcase()的返回值,其实是最终结果的引用。这足以说明,upcase()返回的引用已经指向了一个新的对象,而q仍然在原来的位置。
34+
35+
**String**的这种行为正是我们想要的。例如:
36+
```java
37+
String s = "asdf";
38+
String x = Immutable.upcase(s);
39+
```
40+
难道你真的希望upcase()方法改变其参数吗?对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。在阅读这段代码时,读者自然会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和阅读。
1141
<!-- Overloading + vs. StringBuilder -->
1242
## 重载和StringBuilder
13-
14-
43+
**String**对象是不可变的,你可以给一个**String**对象添加任意多的别名。因为**String**是只读的,所以指向它的任何引用都不可能改它的值,因此,也就不会影响到其他引用。
44+
45+
不可变性会带来一定的效率问题。为**String**对象重载的“+”操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义(用于**String**的“+”与“+=”是Java中仅有的两个重载过的操作符,Java不允许程序员重载任何其他的操作符)。
46+
47+
操作符“+”可以用来连接**String**
48+
```java
49+
// strings/Concatenation.java
50+
51+
public class Concatenation {
52+
public static void main(String[] args) {
53+
String mango = "mango";
54+
String s = "abc" + mango + "def" + 47;
55+
System.out.println(s);
56+
}
57+
}
58+
/* Output:
59+
abcmangodef47
60+
*/
61+
```
62+
可以想象一下,这段代码可能是这样工作的:**String**可能有一个append()方法,它会生成一个新的**String**对象,以包含“abc”与**mango**连接后的字符串。然后,该对象再与“def”相连,生成另一个新的对象,依此类推。
63+
64+
这种方式当然是可行的,但是为了生成最终的**String**对象,会产生一大堆需要垃圾回收的中间对象。我猜想,Java设计者一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题),然后他们发现其性能相当糟糕。
65+
66+
想看看以上代码到底是如何工作的吗?可以用JDK自带的javap来反编译以上代码。命令如下:
67+
```java
68+
javap -c Concatenation
69+
```
70+
这里的-c标志表示将生成JVM字节码。我们剔除不感兴趣的部分,然后做细微的修改,于是有了一下的字节码:
71+
```
72+
public static void main(java.lang.String[]);
73+
Code:
74+
Stack=2, Locals=3, Args_size=1
75+
0: ldc #2; //String mango
76+
2: astore_1
77+
3: new #3; //class StringBuilder
78+
6: dup
79+
7: invokespecial #4; //StringBuilder."<init>":()
80+
10: ldc #5; //String abc
81+
12: invokevirtual #6; //StringBuilder.append:(String)
82+
15: aload_1
83+
16: invokevirtual #6; //StringBuilder.append:(String)
84+
19: ldc #7; //String def
85+
21: invokevirtual #6; //StringBuilder.append:(String)
86+
24: bipush 47
87+
26: invokevirtual #8; //StringBuilder.append:(I)
88+
29: invokevirtual #9; //StringBuilder.toString:()
89+
32: astore_2
90+
33: getstatic #10; //Field System.out:PrintStream;
91+
36: aload_2
92+
37: invokevirtual #11; //PrintStream.println:(String)
93+
40: return
94+
```
95+
如果你有汇编语言的经验,以上代码应该很眼熟(其中的**dup****invokevirtual**语句相当于Java虚拟机上的汇编语句。即使你完全不了解汇编语言也无需担心)。需要重点注意的是:编译器自动引入了**java.lang.StringBuilder**类。虽然源代码中并没有使用**StringBuilder**类,但是编译器却自作主张地使用了它,就因为它更高效。
96+
在这里,编译器创建了一个**StringBuilder**对象,用于构建最终的**String s**,并对每个字符串调用了一次**append()**方法,共计4次。最后调用**toString()**生成结果,并存为**s**(使用的命令为astore_2)。
1597
<!-- Unintended Recursion -->
1698
## 意外递归
1799

0 commit comments

Comments
 (0)