String创建几个对象等一系列问题解析

一开始这个问题并没有想专门写一篇文章,但最近看到boss上的一个提问,让我有专门针对这个知识点写一篇文章的打算。

下面会给出各种代码,随之会给出创建了几个对象的答案,然后便是相应的解释。

问题一:

String s = "a";

创建0个或1个对象
若常量池中已经存在"a",那么就不会再创建对象,直接将引用赋值给s,这样就创建了0个对象;
若常量池中没有"a",就会在常量池中创建一个对象"a",并将引用赋值给s,这样就创建了1个对象。

问题二:

String s = new String("a");

创建1个或2个对象
若常量池中已经存在"a",那么就不会再在常量池中创建对象,而是执行new操作,在堆中创建一个存储"a"的String对象,对象的引用赋值给s,这样就创建了1个对象;
若常量池中没有"a",就会在常量池中创建一个对象"a",然后再执行new操作,在堆中创建一个存储"a"的String对象,对象的引用赋值给s,这样就创建了2个对象。

注意:
这里的情况其实堆中的String对象的char value[]属性指向常量池中的char value[],可以通过debug的形式查看两个String对象的value值的引用相同,也就是说,虽然是两个对象,但它们的value值均指向常量池中的同一个地址
上述过程中检查常量池是否有相同Unicode的字符串常量时,使用的方法便是String中的intern()方法。

public native String intern();

问题三:

new String("abc");

创建1个或2个对象
同上,只是没有赋值给引用的操作。

下面的问题暂且不考虑常量池中是否已经存在对应字符串的问题,假设都不存在对应的字符串。

问题四:

String s = "a" + "b";

这里涉及到字符串常量重载“+”的问题,当一个字符串由多个字符串常量拼接成一个字符串时,它自己也肯定是字符串常量字符串常量的“+”号连接Java虚拟机会在程序编译期将其优化为连接后的值
在编译时已经被合并成“ab”字符串,因此,只会创建1个对象并没有创建临时字符串对象a和b,这样减轻了垃圾收集器的压力

问题五:

String s = "a" + new String("b");

创建5个对象
上述的代码Java虚拟机在编译的时候同样会优化,会创建一个StringBuilder来进行字符串的拼接,实际效果类似:

String s = new String("b");
new StringBuilder().append("a").append(s).toString();

很显然,常量池中分别有“a”和“b”堆中对象new String(“b”)和“ab”,还多出了一个StringBuilder对象,那就应该是5个对象。

疑问
为什么会有人说,StringBuilder最后toString()之后的“ab”难道不在常量池存一份吗?这样不就是6个对象?
解析
答案是不对的
StringBuilder的toString()方法是如何将拼接的结果转化为字符串的:

@Override
public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

很显然,在toString方法中又新创建了一个String对象,而该String对象传递数组的构造方法来创建的:

public String(char value[], int offset, int count)

也就是说,String对象的value值直接指向了一个已经存在的数组,而并没有指向常量池中的字符串
所以,上面答案是创建了5个对象。

附录:

String s1 = "a";
String s2 = new String("b");
String s3 = s1 + s2;
String s4 = "ab";
System.out.println(s3 == s4);//false

为什么会是false呢?
s4很明显存在于常量池中,那么既然s3==s4是false,那么说明s3和s4的物理地址是不一样的,既然s3不在常量池中,那么就是在堆中了。这也是为什么上面会说“ab”是堆对象。


版权声明:本文为qq_16268979原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。