字符串连接符 “+”及字符串常量池实验、字符串final属性
结果预览
public class StrTest{
public static void main(String[] args){
String str1="hello";
String str2="hello"+"wiaoong"; //常量 编译器直接优化为 hellowiaoong
String str3=str1+"wiaoong";
System.out.println(str3==str2); //false
final String str4="hello";
String strFinal=str4+"wiaoong";
System.out.println(strFinal==str2); //true
String str6="hello";
String str7=new String("hello");
System.out.println(str7==str6); //false
String str8=str7.intern();
System.out.println(str6==str8); //true
String str11="123";
String str12=str11;
str11+="456";
System.out.println(str12); //123
System.out.println(str11); //456
}
}
场景一:编译器优化
public class StrTest{
public static void main(String[] args){
String str1="hello";
String str2="hello"+"wiaoong";
String str3=str1+"wiaoong";
}
}
Jvm编译指令:javap -c StrTest.class
这些指令我也是一知半解,但是不妨碍我们分析发生了什么,如果有更好的指令参考文章请留言分享。
附上一份JVM指令博文:JVM指令博客
结果分析:
编译后的JVM指令集
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String hellowiaoong
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: ldc #7 // String wiaoong
19: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: astore_3
26: return
观察指令可以得知:
- str2在编译时已经被编译器给优化了,也就是说连接符“+”连接的是两个常量时 如
"a"+"b"
, 和 "ab"是等价的 - str3在编译的时候Jvm新建了一个StirngBuilder对象,并且分布把常量"wiaoong"和变量str1进行了append操作,最后返回了StirngBuilder.toString()给str3,
也就是说连接符“+”连接中存在变量时,会通过StringBuilder构建新的对象。
由上可知,str3==str2 为false.
场景二:编译器优化
public class StrTest{
public static void main(String[] args){
final String str4="wiaoong";
String str5="hello"+"wiaoong";
String str3="hello"+str4;
System.out.println(str5==str3); //true
}
}
编译后的JVM指令集
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hellowiaoong
2: astore_2
3: ldc #2 // String hellowiaoong
5: astore_3
6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_2
10: aload_3
11: if_acmpne 18
14: iconst_1
15: goto 19
18: iconst_0
19: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
22: return
由上可知,这是常量的一种.
场景三:字符串常量池
public class StrTest{
public static void main(String[] args){
String str6="hello";
String str7=new String("hello");
System.out.println(str7==str6); //false
String str8=str7.intern();
System.out.println(str6==str8); //true
}
}
编译后的JVM指令集
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hello
2: astore_1
3: new #3 // class java/lang/String
6: dup
7: ldc #2 // String hello
9: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V
12: astore_2
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
16: aload_2
17: aload_1
18: if_acmpne 25
21: iconst_1
22: goto 26
25: iconst_0
26: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
29: aload_2
30: invokevirtual #7 // Method java/lang/String.intern:()Ljava/lang/String;
33: astore_3
34: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
37: aload_1
38: aload_3
39: if_acmpne 46
42: iconst_1
43: goto 47
46: iconst_0
47: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
50: return
由上可知:
new 创建字符串时,检测到常量池中已经存在字面量“hello”,直接返回常量池中的应用给new构建新的对象
intern()能完成字符串主动入池操作,如果字符串已经存在字符串常量池中,就返回常量池中字符串的引用地址
str6初始化后,常量池中已经存在hello
了,执行intern()就直接返回引用. 即JVM指令集中的步骤7ldc
操作,所以str6==str8为true
场景4:字符串final分析
public class StrTest{
public static void main(String[] args){
String str11="123";
String str12=str11;
str11+="456";
System.out.println(str12); //123
System.out.println(str11); //123456
}
}
分析:
str11+="456"
这行代码创建了一个新的对象,并且把str11指向了新的匿名String对象的堆内存地址,而str11的堆内存内的内容是没有变化的。
上图:
这里又衍生出一个String str=a+b+c+d;创建了几个对象的面试常见问题?
分析:
public StringBuilder() {
super(16);
}
public StringBuffer() {
super(16);
}
初始化char[]大小是16,如果超过这个长度则会进行char[]扩容,new一个新的char[]
算上JVM自动创建的StringBuilder(),结果是2-3个
如果字符长常量池中没有这个字符串,那么又会新建一个对象