首先看下在日常开发中我们操作字符串拼接的集中方式:
public static void main(String[] args) {
String s1 = "a"+"b"+"c";
String s2 = "abc";
String s3 = s2+"";
final String s4 = "abc";
String s5 = s4+"";
System.out.println("s1==s2:" + (s1 == s2)); //true
System.out.println("s2==s3:"+ (s2 == s3)); //false
System.out.println("s4==s5:"+ (s4 == s5)); //true
}
为什么不同的方式得出的结构不一样呢?我们先来看看这段代码编译后的结果:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=6, args_size=1
0: ldc #16 // String abc
2: astore_1
3: ldc #16 // String abc
5: astore_2
6: new #18 // class java/lang/StringBuilder
9: dup
10: aload_2
11: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
14: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
17: invokevirtual #29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
20: astore_3
21: ldc #16 // String abc
23: astore 4
25: ldc #16 // String abc
27: astore 5
29: getstatic #33 // Field java/lang/System.out:Ljava/io/PrintStream;
32: new #18 // class java/lang/StringBuilder
35: dup
36: ldc #39 // String s1==s2:
38: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
41: aload_1
42: aload_2
43: if_acmpne 50
46: iconst_1
47: goto 51
50: iconst_0
51: invokevirtual #41 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
54: invokevirtual #29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
57: invokevirtual #45 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
60: getstatic #33 // Field java/lang/System.out:Ljava/io/PrintStream;
63: new #18 // class java/lang/StringBuilder
66: dup
67: ldc #50 // String s2==s3:
69: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
72: aload_2
73: aload_3
74: if_acmpne 81
77: iconst_1
78: goto 82
81: iconst_0
82: invokevirtual #41 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
85: invokevirtual #29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
88: invokevirtual #45 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
91: getstatic #33 // Field java/lang/System.out:Ljava/io/PrintStream;
94: new #18 // class java/lang/StringBuilder
97: dup
98: ldc #52 // String s4==s5:
100: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
103: ldc #16 // String abc
105: aload 5
107: if_acmpne 114
110: iconst_1
111: goto 115
114: iconst_0
115: invokevirtual #41 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
118: invokevirtual #29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
121: invokevirtual #45 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
124: return
LineNumberTable:
line 5: 0
line 6: 3
line 8: 6
line 10: 21
line 11: 25
line 13: 29
line 14: 60
line 15: 91
line 16: 124
注意这些行号,与编译后的 LineNumberTable有关系
s1 == s2为true
首先看编译后的第一个指令:
0: ldc #16 // String abc
通过LineNumberTable与代码的对应关系可以看出
line 5: 0
第0个指令对应我们代码的第5行,也就是
String s1 = "a"+"b"+"c";
可以看出编译时 “a”+“b”+"c"直接编译成了“abc”,所以s1 == s2为true。
s2 == s3为false
s3在代码的第8行,通过LineNumberTable查看对应的是编译后的6,编译后的处理方式是:
6: new #18 // class java/lang/StringBuilder
可以看到是通过new StringBuilder产生的对象(如果拼接的不是空字符串,会调用append方法),s2放在字符串常量池,s3是通过new出来的放在堆空间中(虽然常量池也在堆空间内,但是s3并不是放在堆中的常量池里),所有s2 == s3为false。
关于字符串常量池等一些其它问题可以查看JVM虚拟机运行时数据区のStringTable 字符串常量池
s4 == s5为true
通过查看编译后文件,看出s5再编译中直接赋值了,由此可以看出有final修饰过的变量(叫做常量),在编译期就已经替换为常量了。所以s4 == s5为true。
由此可以得出
- 直接有字符串拼接,或者常量(由final修饰)直接拼接,在编译就已经优化为最终的字符串
- 有变量与变量,变量与字符串拼接的在编译期会编译为StringBuilder来实现。