关于字符串拼接的问题

一、分析以下代码:

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)); // true
		System.out.println("str1 == str3 ?? : " + (str1 == str3)); // false
		System.out.println("str1 == str4 ?? : " + (str1 == str4)); // false
		System.out.println("str3 == str4 ?? : " + (str3 == str4)); // false
		System.out.println("str1 == str5 ?? : " + (str1 == str5)); // true
		System.out.println("str3 == str5 ?? : " + (str3 == str5)); // false

		final String pig = "length: 10";
		final String dog = "length: " + pig.length(); 
		final String duck = "length: " + 10;
		System.out.println("pig==dog ?? : " + (pig == dog));// false
		System.out.println("pig==duck ?? : " + (pig == duck));// true

	}

}


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

public class StringAddTest
{

    public StringAddTest()
    {
    }

    public static void main(String args[])
    {
	String str = "hello";//编译前:String str = "hello";
        String str1 = "hello000";//编译前: String str1 = "hello000";

	//直接拼接
        String str2 = "hello000";//编译前:String str2 = "hello" + "000";
	//创建了新的对象
        String str3 = (new StringBuilder("hello")).append(new String("000")).toString();//编译前:String str3 = "hello" + new String("000"); 
	//创建了新的对象
        String str4 = (new StringBuilder(String.valueOf(str1))).append("000").toString();//编译前:String str4 = str1 + "000"; 
	//创建了新的对象,但是使用了intern方法
	String str5 = (new StringBuilder(String.valueOf(str))).append("000").toString().intern();//编译前:String str5 = (str + "000").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";//编译前:final String pig = "length: 10";
	//创建了新的对象
        String dog = (new StringBuilder("length: ")).append("length: 10".length()).toString();//编译前:final String dog = "length: " + pig.length(); 
	//直接拼接
        String duck = "length: 10";//编译前:final String duck = "length: " + 10;
	
	//以下属于输出的内容可以忽略不看
 	System.out.println((new StringBuilder("pig==dog ?? : ")).append("length: 10" == dog).toString());
 	System.out.println("pig==duck ?? : true"); 
	}
}

二:字符串拼接规律总结:

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


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


三、拓展

1. 字符串的不可变性(不考虑反射)

    我们要知道,字符串是不可变的。也就是说,字符串一旦创建之后就不能修改,没有任何方法可以改变某个字符串。比如说,String s1 = “aaa”;String s2 = “a”+s1;s2实际上是指向了另一个字符串“aaaa”,假设此时GC没有运行,则内存中仍然存在“aaa”这个字符串。

2. java的几个“常量池”
    Java中有几个常量池用以存放"字符串",分别是class文件常量池(class constant pool)、运行时常量池(runtime constant pool)、全局字符串常量池(string constant pool 或者 string pool)。
    a.每个类都会有一个class文件常量池。class文件常量池,用以存放各种字面值常量Literal及符号引用Symbolic References。

    b.每个类都会有一个运行时常量池。运行时常量池里存放的是,class文件常量池里的字面值跟符号引用。在jvm完成类加载之后,这些字面值跟符号引用就会进入运行时常量池,在完成解析之后,符号引用会被替换为直接引用,解析过程中jvm会去全局字符串常量池中查询,以保证两个常量池中对字符串的引用一致(真是如此吗,有何意义)。

    c.全局字符串常量池是所有类共享的。全局字符串常量池的内容是在类加载完成后,经过验证准备阶段后,在堆中生成的字符串实例的引用。也就是说,runtime constant pool与string constant pool中的值都是对字符串对象的引用值。
3. new String("bb")创建了几个对象

    new关键字返回的String对象的引用并不会直接进入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。

    所以,全局字符串常量池中原本不存在"bb",则new String("bb")会创建两个对象;如果已经存在,则只创建一个


四、参考:

拓展的内容,都是摘自几位大神写的文章,链接如下,希望看到这篇文章的读者能够读一下,相信各位能获益不少。
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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值