字符串拼接在日常中编码中大量使用,但是对底层的原理却缺乏理解。
我们还是从一段代码开始:
public static void main(String[] args) throws Exception{ String s1="a"; String s2="b"; String s3="ab"; String s4=s1+s2; System.out.println(s3==s4); }
运行输出:
false
我们反编译字节码看看:
javap -v Jvm1_22
选取关键部分:
0: ldc #2 // String a 2: astore_1 3: ldc #3 // String b 5: astore_2 6: ldc #4 // String ab 8: astore_3 9: new #5 // class java/lang/StringBuilder 12: dup //复制一份对象引用到栈顶 13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V 执行构造方法 16: aload_1 17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 20: aload_2 21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: astore 4 29: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
我们解读如下:
前面字符串"a" “b” "ab"的生成没啥问题,我们关注 String s4=s1+s2的执行过程,
从第9-13行,我们看到new 关键字,其实是创建了StringBuilder对象
16行加载了第一个槽位中的数a
17行调用了append的操作
10行加载了第二个槽位中的数,21行调用了append的操作
24行调用了tostring的方法,27行则把对象进行保存。
我们再进一步看StringBuilder中的toString方法:
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
可以很清楚看到,这里会利用拼接好的值new一个新的String对象,所以我们最终拼接的结果会是一个新对象,这就是a+b的生成过程,可以很好解释s3==s4结果是false。
总结
字符串的+ 拼接操作其实底层会调用StringBuilder拼接一个新的字符串对象出来,有new的操作,返回的是一个新的对象。
进一步理解
我们看看另一种情况:
public static void main(String[] args) throws Exception{ String s1="a";//用到了这个对象,才开始创建这个对象,行为上面是懒惰的 String s2="b"; String s3="ab"; String s4=s1+s2; System.out.println(s3==s4); String s5="a"+"b"; System.out.println(s5==s3); }
反编译结果如下:
32: aload_3 33: aload 4 35: if_acmpne 42 38: iconst_1 39: goto 43 42: iconst_0 43: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V 46: ldc #4 // String ab 48: astore 5 50: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 53: aload 5
我们看到46行,字符串直接就取了ab的值,自然输出结果就是true。
这里的解释是javac在编译期的优化,它认为"a" “b"都是常量,拼接的结果在编译期间就确定为"ab”,不可能再变回了,上一行代码是引用的值,有可能发现修改。