一、分析以下代码:
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