经典面试题 (jdk 1.6)
String str1 = new String("11");
System.out.println(str1.intern() == str1); // false
String str = new String("1") + new String("1");
System.out.println(str.intern() == str); // false
jdk 1.7
String str1 = new String("11");
System.out.println(str1.intern() == str1); // false
String str = new String("2") + new String("1");
System.out.println(str.intern() == str); // true
为什么上面两道题,intern()返回的结果不一致?
在jdk1.7以前,字符串常量池存在于方法区中
此时我们调用intern(),他最终会返回该字符串在常量池中的地址(首先判断常量池中是否存在,如果存在直接返回常量池中字符串的地址,不存在则在常量池中新建该字符串并将新建地址返回)。
在jdk1.7及以后,字符串常量池存在于堆空间内
此时调用intern()时,他会先查看堆空间中是否存在该字符串,如果存在,常量池直接引用堆空间中该字符串的地址,不存在才会在常量池中新建地址并且返回。
s1 = "a";
s2 = "b";
s3 = "ab";
s4 = s1 + s2;
执行s1 + s2的过程实际操作是new StringBuilder().append("a").append("b").toString()(这属于编译器优化)。StringBuilder中的toString()源码如下,
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
因此s1 + s2就相当于new String(“ab”),重新new了一个String实例,是在堆上开辟了一块内存存储这个字符串,因此s3和s4不相等。
当执行到指令ldc #2时,才会把符号a变为“a”字符串对象(这叫字符串的延迟加载)
s3 = "ab";
s5 = "a" + "b";
从反编译结果可以看出,"a" + "b"是直接去StringTable中找字符串“ab”,而不是先找“a”,再找“b”,最后再拼接,可以看到s5变量的取值,和s3变量的取值是一样的过程。因此s3和s5值相等。
s6 = s4.intern();
先去局部变量表中Slot为4位置的数据(“ab”,这个“ab”字符串时堆上的,因为取的是s4的值),然后调用intern()方法尝试将这个字符串放入StringTable中,如果存在,则不放入,直接拿来用,否则放入,并返回StringtTable中的这个对象(关于intern()具体知识看下边的例子)。
上边是JDK7及其之后的情况,JDK6的时候,执行s.intern(),如果常量池中没有和s相同值的字符串,则会重新创建一个字符串,把这个字符串放到常量池中,并没有使用s指向的那块空间,所以常量池中的"ab"和s并不相等。
《深入理解java虚拟机》这本书上出现了同样的题目:
而答案如下:
可是问题又来了,哪儿出现的“java”这个字符串呢?明明我只运行了一个main方法啊?
答案就是:
sun.misc.Version类!
sun.misc.Version 类会在JDK类库的初始化过程中被加载并初始化,而在初始化时它需要对静态常量字段根据指定的常量值(ConstantValue)做默认初始化,此时被 sun.misc.Version.launcher 静态常量字段所引用的"java"字符串字面量就被intern到HotSpot VM的字符串常量池——StringTable里了。
private static final String launcher_name = "java";
private static final String launcher_name = "@@launcher_name@@";
问题五:那么这段代码又创建了几个对象?
String str3 = new String("a") + new String("a");
答案是五个
因为使用+号的String字符串拼接,底层其实都是先创建一个StringBuilder对象,然后调用append方法把要+的字符串都append进去,最后toString创建一个新的String对象如下图:
红色的地方就是new出来对象的语句,而绿色则是两次append
四个红色一共四个对象,再加上字符串常量池上创建的"a"对象,一共五个
这也正是为什么阿里巴巴代码规范中不建议在for循环里使用+号拼接字符串