前言
最近参加了全国计算机能力挑战赛,其中有一道关于String类的选择题我很感兴趣:
String a = "Hello"; //新建了一个对象
String b = a; //没有创建对象,将a的引用赋值给了b
System.out.println(a == b); //true
a = a + b; //? 这个创没创建String对象呢?
问上述共创建了几个String对象?
字符串常量池
字符串在实际项目中使用的还是很频繁的,如果我们在项目开发过程中需要频繁的创建相同的字符串就会导致效率低下。为了解决这一问题,Java给出了字符串常量池的解决方案。我们可以将字符串常量池理解为一个jvm提供给我们的系统级缓存。当我们使用String str = "str"
这种形式创建对象时,Java就会先从字符串常量池中查找是否已经存在这个字符串,如果存在就将这个常量池中字符串对象的引用返回,否则就new一个字符串对象,并将该字符串对象存入常量池中。
需要说明的是通过String str = new String("str")
创建的字符串对象是不会被存入常量池内的。
堆区(JVM中只有一个堆区被所有的线程共享):
堆区存储的是对象,堆区不存放对象的引用以及基本数据类型,只存放对象本身,对象由垃圾回收器自动进行回收。因此大小和生命周期不确定。
栈区(每个线程包含一个栈区):
栈中存放基本数据类型以及对象的引用。栈中的数据(原始类型和对象引用)都是私有的。
栈分为3部分:基本类型变量区、执行环境上下文、操作指令区
方法区(和堆区一样被所有的线程共享):
方法区中包含的都是整个程序中永远唯一的元素,例如class,static变量。
字符串常量池就存在于方法区中。
字符串对象的创建
String类被final修饰,这也就意味着String对象创建以后就不允许被修改。当我们使用String类提供的replace()
等方法修改字符串时String中相应方法会重新创建一个字符串对象并将该字符串对象的引用返回。
使用双引号创建字符串
例如String str = "str"
- 在常量池中查找是否含有“str”对象
- 如果包含则返回该字符串对象的引用
- 如果不包含则在堆内存创建相应的对象实例并将对象实例存入字符串常量池中
所以上述可能创建一个String对象,也可能不创建String对象。因此最多创建一个String对象
使用new关键字创建字符串
例如String str = new String("str")
- 在字符串常量池中查找是否包含"str"对象
- 如果包含则返回该字符串对象的引用
- 如果不包含则在堆内存创建相应的对象实例并存入字符串常量池中
- 在堆内存创建
new String("str")
对象 - 将对象地址赋值给str,在栈内存创建一个引用
所以,常量池中没有“str”对象则创建两个对象,否则只创建一个对象。
字符串中的+号
在实际项目开发中我们也经常使用“+”号来连接字符串,我们看一下下面的案例:
String str1 = "HappyLyf";
String str2 = "Happy"+"Lyf";
System.out.println(str1 == str2);
输出结果:
true
由于“Happy”和“Lyf”都是常量,编译时,String str2 = "Happy"+"Lyf";
会被自动编译为String str2 = "HappyLyf";
而此时字符串常量池中已经存在"HappuLyf"
对象,所以字符串常量池会将该字符串对象的引用返回。
再看一个案例:
String str3 = "HelloZw";
String str4 = "Hello";
String str5 = "Zw";
String str6 = str4+str5;
System.out.println(str3 == str6);
输出结果:
false
当执行到String str6 = str4 + str5
的时候,实际上Java底层是通过StringBuilder
对象的append
方法来完成字符串的拼接的。我们可以使用jad反编译工具来查看一下:
总结
回到前言中的问题。
String a = "Hello"; //新建了一个对象
String b = a; //没有创建对象,将a的引用赋值给了b
System.out.println(a == b); //true
a = a + b;
-
String a = "Hello"
在堆内存中新建了一个对象,并将对象存入字符串常量池中 -
String b = a
没有创建对象,只是将a的引用赋值给了b,引用存放在栈内存中 -
a = a+b
创建了几个对象呢?首先根据反编译结果,Java底层会生成一个
StringBuilder
对象来连接字符串,StringBuilder
对象在连接完成后会调用toString()
方法生成一个String对象。这就生成了两个对象:一个String
对象,一个StringBuilder
对象。但是这里我有一个疑问:有博主提出
a+b
这里会通过复制从字符串常量池中复制新的对象到堆内存中,这样这里是否会生成两个新的字符串对象呢?