在一次面试中,被问到这方面的问题,搞得我一脸蒙逼,那时候还没去了解JVM,为了以后不再出现这样的情况,我就在这里好好梳理一下String创建会创建多少个对象呢?
先看代码
public static void main(String[] args) {
String str1 = "abc";
String str2 = "a" + "b" + "c";
System.out.println("结果1:" + (str1 == str2));
String str3 = "a";
String str4 = str3 + "bc";
System.out.println("结果2:" + (str1 == str4));
System.out.println("结果3:" + (str1 == str4.intern()));
final String str5 = "a";
String str6 = str5 + "bc";
System.out.println("结果4:" + (str1 == str6));
}
代码运行结果:
结果1:true
结果2:false
结果3:true
结果4:true
上面的运行结果需要从两个方面来说明:
一:java编译器
二:java字符串常量池
java编译器
我们来看看java编译器将上面的源码编译成了什么样子。(反编译结果)
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
System.out.println("结果1:" + (str1 == str2));
String str3 = "a";
String str4 = str3 + "bc";
System.out.println("结果2:" + (str1 == str4));
System.out.println("结果3:" + (str1 == str4.intern()));
String str5 = "a";
String str6 = "abc";
System.out.println("结果4:" + (str1 == str6));
}
源代码和编译后的代码有很多不同点。str2和str6都变成了直接赋值为"abc",而str4却没有变化,这里也是java新手不太清楚的地方,其实由于常量字符串是在编译的时候就是可以被确定的,又因"a","b"和"c"都是字符串常量,因此变量str2的值在编译时就可以确定,同理因为str5被final关键字修饰,也相当于字符串常量,因此变量str6的值也在编译时就可以确定,代码编译后就和String str="abc"
一样了。
也是因为上面的原因str4没有改变,因为str4并不能在编译期确定。
java字符串常量池
在执行代码
String str1 = "abc"
时,JVM先去字符串常量池中查找,看是否已经存在值为”abc”的对象,如果存在,则不再创建新的对象,而直接返回已存在对象的引用;反之,则先创建一个新的对象,然后加入到字符串池中,再将其引用返回。
String str1 = "abc"; // 会创建一个新的对象"abc"在字符串常量池中
String str2 = "abc"; // 因为字符串常量池中已经存在"abc",所以不会创建对象
这样结果1
为true
,就可以理解了
String str3 = "a"; // 会创建一个新的对象"abc"在字符串常量池中
/*
下面这行代码一共会创建三个对象:
1. 会在字符串常量池中创建一个"bc"对象
2. str3与 "bc" 进行字符串连接时,底层通过StringBuffer进行连接,生成一个
StringBuffer对象(StringBuffer内部字符不考虑)
3. 然后通过StringBuffer的append方法连接,通过toString()方法,将StringBuffer对象
转为String,此时会产生一个新的堆内存地址,str4指向这个新的内存地址。
*/
String str4 = str3 + "bc";
// 因为str4在堆内存变量中,str1在字符串常量池中,所以==地址比较为false
System.out.println("结果2:" + (str1 == str4));
// str4.intern() 返回在字符串常量池中的"abc"引用,就和str1是同一对象,为true
System.out.println("结果3:" + (str1 == str4.intern()));
看完上面的代码相信您已经对字符串创建对象个数有了一定的了解,接下来我们再来看看另一种情况。
/*
下面的代码,一共会创建两个对象:
1. 在字符串常量池中创建"abc"对象
2. 在堆中创建的原实例字符串对象
*/
String str1 = new String("abc");
// str1只是java栈中的对象引用
注意:String
类型的引用是存在栈里。而字符串"abc"
是存在字符串常量池中
// 在字符串常量池中创建一个"abc"对象
String str1 = "abc";
// 由于"abc"已经在字符串常量池中已经存在,所以不会再创建。
// 使用new关键字创建字符串,一定会在堆中创建一个实例,所以下面代码会创建一个对象
String str2 = new String("abc");
// str1只是java栈中的对象引用
注意:String
类型的引用是存在栈里。而字符串"abc"
是存在字符串常量池中
下面提升难度
public static void main(String[] args) {
String str1 = "a" + new String("b");
String str2 = "ab";
System.out.println(str1 == str2);
String str3 = str2 + "c";
str3.intern();
String str4 = "abc";
System.out.println(str3 == str4);
}
运行结果:
false
true
注意:上面的运行结果和jdk版本有关,1.7和1.8都是上面的结果,1.7以下不是
上面的结果重点就是intern方法的作用,这里我只是抛砖引玉,想了解更多的读者可以看看下面的这篇博客:
深入解析String#intern
笔者个人能力有限,如有错误,请联系我修改。
网友评论