关于字符串拼接的问题

来源:互联网 发布:阿里云 腾讯云 香港 编辑:程序博客网 时间:2024/05/15 13:07

分析以下代码:

public class StringAddTest {public static void main(String[] args) {String str = "hello";String str1 = "hello000";String str2 = "hello" + "000"; String str3 = "hello" + new String("000"); String str4 = str1 + "000"; String str5 = (str + "000").intern();System.out.println("str1 == str2 ?? : " + (str1 == str2)); // trueSystem.out.println("str1 == str3 ?? : " + (str1 == str3)); // falseSystem.out.println("str1 == str4 ?? : " + (str1 == str4)); // falseSystem.out.println("str3 == str4 ?? : " + (str3 == str4)); // falseSystem.out.println("str1 == str5 ?? : " + (str1 == str5)); // trueSystem.out.println("str3 == str5 ?? : " + (str3 == str5)); // falsefinal String pig = "length: 10";final String dog = "length: " + pig.length(); final String duck = "length: " + 10;System.out.println("pig==dog ?? : " + (pig == dog));// falseSystem.out.println("pig==duck ?? : " + (pig == duck));// true}}



经过与朋友的一些讨论与摸索,总结字符串拼接的规则:
1、 编译器总是会为程序处理一些力所能及的事情,对于一些很直观的计算,比如a = 1+1 这样的计算,Java在编译器阶段就会为我们计算好结果(又或者如str = “abc” + "def"也是在编译阶段就帮我们拼接好了)。
2、当这个编译器无法直接为我们拼接字符串的时候,要等到jvm运行程序的时候才会重新创建一个字符串对象,用以存放拼接后的字符串。
3、字面值常量放在常量池,hello000这个字符串就放在常量池中,str1指向了常量池中的hello000字符创,而当我们给另外一个变量赋值hello000的时候,实际上是让这个变量也指向这个hello000的字符串
4、“==”比较的是地址或者数字的值
根据这样的规则,我们已经基本可以正确分析上面代码并得到正确的打印结果了。

String str = "hello";//str1指向常量池中的字符串hello000String str1 = "hello000"; // 编译时直接进行字符串拼接,然后在常量池中查找,发现hello000已经在常量池中,故将str2指向hello000String str2 = "hello" + "000";// 这里要等到运行时才会创建一个000的字符串对象, 所以这里str3最终只会指向堆上对象String str3 = "hello" + new String("000"); // 因为编译器不知道无法直接看到str1这个变量的值,这个属于他力所不及的范围所以不会直接拼接。String str4 = str1 + "000"; //检查字符串常量池中是否有等于该字符串的引用,有的话,就返回该字符串的引用,否则就将该字符串添加到池中String str5 = (str + "000").intern();//pig指向常量池中的length: 10final String pig = "length: 10";//pig.length()要经过计算才知道,这个要等到运行时才可得到结果,所以,编译器不会直接拼接final String dog = "length: " + pig.length(); //直接拼接final String duck = "length: " + 10;



利用反编译工具来分析上面的代码:



public class StringAddTest{    public StringAddTest()    {    }    public static void main(String args[])    {String str = "hello";        String str1 = "hello000";//直接拼接        String str2 = "hello000";//创建了新的对象        String str3 = (new StringBuilder("hello")).append(new String("000")).toString();//创建了新的对象        String str4 = (new StringBuilder(String.valueOf(str1))).append("000").toString();//创建了新的对象,但是使用了intern方法String str5 = (new StringBuilder(String.valueOf(str))).append("000").toString().intern();//以下属于输出的内容可以忽略不看        System.out.println((new StringBuilder("str1 == str2 ?? : ")).append(str1 == str2).toString());        System.out.println((new StringBuilder("str1 == str3 ?? : ")).append(str1 == str3).toString());        System.out.println((new StringBuilder("str1 == str4 ?? : ")).append(str1 == str4).toString());        System.out.println((new StringBuilder("str3 == str4 ?? : ")).append(str3 == str4).toString());System.out.println((new StringBuilder("str1 == str5 ?? : ")).append(str1 == str5).toString());     System.out.println((new StringBuilder("str3 == str5 ?? : ")).append(str3 == str5).toString());        String pig = "length: 10";//创建了新的对象        String dog = (new StringBuilder("length: ")).append("length: 10".length()).toString();//直接拼接        String duck = "length: 10";//以下属于输出的内容可以忽略不看 System.out.println((new StringBuilder("pig==dog ?? : ")).append("length: 10" == dog).toString()); System.out.println("pig==duck ?? : true"); }}







关于字符串的一些拓展知识:
我们要知道,字符串是不可变的。也就是说,字符串一旦创建之后就不能修改,比如说,String s1 = “aaa”;String s2 = “a”+s1;s2实际上是指向了另一个字符串“aaaa”;没有任何方法可以改变某个字符串。
Java中有几个常量池用以存放"字符串",分别是class文件常量池(class constant pool)、运行时常量池(runtime constant pool)、全局字符串常量池(string constant pool 或者 string pool)。
每个类都会有一个class文件常量池。class文件常量池,用以存放各种字面值常量Literal及符号引用Symbolic References。
每个类都会有一个运行时常量池。运行时常量池里存放的是,class文件常量池里的字面值跟符号引用。在jvm完成类加载之后,这些字面值跟符号引用就会进入运行时常量池,在完成解析之后,符号引用会被替换为直接引用,解析过程中jvm会去全局字符串常量池中查询,以保证两个常量池中对字符串的引用一致(真是如此吗,有何意义)。
全局字符串常量池是所有类共享的。全局字符串常量池的内容是在类加载完成后,经过验证准备阶段后,在堆中生成的字符串实例的引用。也就是说,runtime constant pool与string constant pool中的值都是对字符串对象的引用值。
new关键字返回的字符创对象引用并不会直接进入string pool(可以使用String.intern()方法达到这个效果),这与向一个变量直接赋值一个字符串字面值常量不同。String s1 = “aaa”,在解析阶段会,jvm会现在string constant pool 中查找,看看是否有equal“aaa”的引用,有,就直接将该引用赋值给s1,如果没有,就在堆中创建一个aaa的字符串对象,然后将其引用驻留interned在字符串常量池中。而String s3  = new String("bb"),它同样也会在string constant pool中查找有没有equal“bb”的对象的引用,找不到,就会在堆中创建一个与字面值常量“bb”对应的的实例对象,并将其引用驻留到string constant pool ,然后再创建一个内容为“bb”的实例对象,并将其引用赋值给s3。


拓展的内容,都是摘自几位大神写的文章,链接如下,希望看到这篇文章的读者能够读一下,相信各位能获益不少。
http://rednaxelafx.iteye.com/blog/774673
https://www.zhihu.com/question/29884421
https://www.zhihu.com/question/55994121
http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/
https://javaranch.com/journal/200409/ScjpTipLine-StringsLiterally.html


原创粉丝点击