底层原理分析
String的两种初始化形式是有本质区别的。
String str1 = "abc"; // 在常量池中
String str2 = new String("abc"); // 在堆上
当直接赋值时,字符串“abc”会被存储在常量池中,只有1份,此时的赋值操作等于是创建0个或1个对象。如果常量池中已经存在了“abc”,那么不会再创建对象,直接将引用赋值给str1;如果常量池中没有“abc”,那么创建一个对象,并将引用赋值给str1。
那么,通过new String(“abc”);的形式又是如何呢?答案是1个或2个。
当JVM遇到上述代码时,会先检索常量池中是否存在“abc”,如果不存在“abc”这个字符串,则会先在常量池中创建这个一个字符串。然后再执行new操作,会在堆内存中创建一个存储“abc”的String对象,对象的引用赋值给str2。此过程创建了2个对象。
当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的String对象,此过程只创建了1个对象。
在上述过程中检查常量池是否有相同Unicode的字符串常量时,使用的方法便是String中的intern()方法。
public native String intern();
下面通过一个简单的示意图看一下String在内存中的两种存储模式。
上面的示意图我们可以看到在堆内创建的String对象的char value[]属性指向了常量池中的char value[]。
还是上面的示例,如果我们通过debug模式也能够看到String的char value[]的引用地址。
图中两个String对象的value值的引用均为{char[3]@1355},也就是说,虽然是两个对象,但它们的value值均指向常量池中的同一个地址。当然,大家还可以拿一个复杂对象(Person)的字符串属性(name)相同时的debug结果进行比对,结果是一样的。
深入问法
如果面试官说程序的代码只有下面一行,那么会创建几个对象?
new String("abc");
答案是2个?
还真不一定。之所以单独列出这个问题是想提醒大家一点:没有直接的赋值操作(str=“abc”),并不代表常量池中没有“abc”这个字符串。也就是说衡量创建几个对象、常量池中是否有对应的字符串,不仅仅由你是否创建决定,还要看程序启动时其他类中是否包含该字符串。