String讲解

API文档描述

Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared.
You’ll see that every method in the class that appears to modify a String actually creates and returns a brand new String object containing the modification. The original String is left untouched.

“+” 重载

在String中,+被赋予了新的功能。该功能可以实现String字符串的连接。在对String的+操作进行反编译时,可知其内部是如何工作的。

1
2
3
4
5
6
7
8
//源码
public class Concatenation {
public static void main(String[] args) {
String mango = "mango";
String s = "abc" + mango + "def" + 47;
System.out.println(s);
}
}

使用javac Concatenation.java命令获取class文件,然后使用javap -c Concatenation命令得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Concatenation {
public Concatenation();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String mango
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String abc
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String def
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: bipush 47
26: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
29: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: astore_2
33: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_2
37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return
}

由上可知,编译器创建了StringBuilder对象去构建源码中的字符串s。其实现是调用append()方法实现字符串的连接,最后调用toString()方法生成结果,把它存储作为s。

简单程序中,可以发现使用+或者 StringBuilder 进行字符串连接时,并没有什么区别,使用哪个都可以。但是在有循环操作时,StringBuilder 比较有优势。看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class WhitherStringBuilder {
//循环内,使用+操作进行字符串连接
public String implicit(String[] fields) {
String result = "";
for(int i = 0; i < fields.length; i++)
result += fields[i];
return result;
}
//循环内,使用StringBuilder操作进行字符串连接
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for(int i = 0; i < fields.length; i++)
result.append(fields[i]);
return result.toString();
}
}

反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public Concatenation();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
//使用+操作进行字符串连接
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5 //重点,跳转到5
38: aload_2
39: areturn

//使用StringBuilder操作进行字符串连接
public java.lang.String explicit(java.lang.String[]);
Code:
0: new #3 // class java/lang/StringBuilder
3: dup
4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12: arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: goto 10 //重点,跳转到第10 步
30: aload_2
31: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn
}

在反编译代码中,在implicit()(即使用+操作字符串)方法中,重点注意第 8:35:。8:表示:如果循环完毕,在执行38:,否则往下执行。35:表示:跳转到循环开始出5:。重点是StringBuilder的创建发生在11:,位于循环之内,因此可知在使用+操作字符串时,会产生许多StringBuilder临时对象,性能不太好。

explicit()(即使用StringBuilder操作字符串)方法中,StringBuilder的创建发生在循环之外,性能好些。

因此得知,在涉及到循环时,建议使用StringBuilder连接字符串。

switch对字符串支持的实现

在java 7之前,switch语句的判断条件可以接受intbytecharshort,不能接受其他类型。但是在Java 7 之后,switch语句可以支持String类型了。switch对String的支持是通过使用equals()方法和hashcode()方法实现的

Java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {

public static void main(String[] args) {
String str = "world";
switch (str) {
case "hello":
System.out.println("hello");
break;
case "world":
System.out.println("world");
break;
default: break;
}
}
}

编译后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test {
public Test() {
}

public static void main(String[] args) {
String str = "world";
byte var3 = -1;
switch(str.hashCode()) { //在switch语句中使用了String的hashCode()方法
case 99162322:
if(str.equals("hello")) { //在哈希值相同的情况下,可能会发生哈希碰撞,因此通过使用equals()方法比较进行安全检查,这个检查是必要的
var3 = 0;
}
break;
case 113318802:
if(str.equals("world")) {
var3 = 1;
}
}

switch(var3) {
case 0:
System.out.println("hello");
break;
case 1:
System.out.println("world");
}

}
}

从上面编译后的代码看,可以知道字符串的switch是通过equals()和hashCode()方法来实现的。switch语句中只能使用整型。使用hashCode()的返回值进行switch语句判断,在哈希值匹配后,由于哈希碰撞,在使用equals()进行精确判断,实现流程控制。

Fork me on GitHub