该问题解析需要点前提知识
1、new String("a") 一共创建了两个对象,一个new 对象存在堆空间中,一个“a”存在堆空间的字符串常量池中。
2、关于字符串拼接操作
@Test
public void test1(){
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = "a" + "b";
String s5 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5)//false
}
@Test
public void test2(){
final String s1 = "a";
fianl String s2 = "b";
String s3 = "ab";
String s4 = "a" + "b";
String s5 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5)//true
}
test1()字节码 test2()字节码
通过两个方法的字节码文件和反编译字节码文件可以看到
1.如果拼接符号左右有至少一个是String类型的变量,就会创建一个StringBuilder对象,在使用对象的append方法将左右两边的数据添加到该对象中,再使用toString()方法,toString()方法会生成一个新的String对象,再将String对象的索引传给赋值符号右边的变量
2.如果拼接符号左右都是字符串常量和常量引用,则仍然使用编译期优化,即非StringBuilder的方式
3、intern()方法
1.作用
查询字符串常量池中是否具有该字符串,如果有,返回该字符串的索引,如果没有,在字符串常量池中创建该字符串然后返回索引
2.jdk6 和 jdk7之后的改变(区别的原因在于字符串常量池存储位置的改变)
jdk6中字符串常量池仍然保存在方法区中,此时调用intern(),如果该字符串不存在于字符串常量池中,会在池中创建该字符串并返回地址值 。jdk7以后,字符串常量池保存在堆空间中,此时调用intern(),如果该字符串不存在于字符串常量池中且堆空间拥有保存该值的String对象, 则字符串常量池中会保存该String对象的地址,此后,如果有String对象使用字面量的方式定义,该对象直接指向了堆空间中保存该字面量的String对象。
验证代码如下:
StringBuilder stringBuilder = new StringBuilder();
//不能直接使用字面量"123"不然会在常量池中创建该字符串
stringBuilder.append("1");
stringBuilder.append("2");
stringBuilder.append("3");
String s5 = stringBuilder.toString();
s5.intern();
String s = "123";
System.out.println(s == s5); //jdk7 true jdk6 false
4、面试题
String s3 = new String("a") + new String("b");
s3.intern();
String s4 = "ab";
System.out.println(s3 == s4); //jdk 6 false jdk 7以后true
该段代码一共创建了6个对象:"a" "b" 两个new出来的String对象 StringBuilder对象 StringBuilder.toString()中创建的String对象 (详情见字符串拼接操作)
那为什么字符串常量池中没有"ab"的创建呢?主要在于StringBuildertoString()方法中创建String对象的构造方法与new String("ab")的构造方法不同。
#StringBuilder的toString()方法
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
#toString调用的构造器
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
#new String 调用的构造器
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
从上面可以清晰的看出toString()调用的构造方法是直接复制StringBuilder数组中的值,并没有使用到字面量"ab",字符串中常量池中不会创建字符串"ab",满足了字符串不存在于字符串常量池中 。调用构造方法后会在堆空间生成保存"ab"的String对象,结合intern()jdk6和jdk7之前的改变可知,s4直接指向了s3对象实体的位置,所以s3 == s4 为true