Java String intern

1
1 背景Java String intern经常出现在面试题中,测试两个字符串是不是同一个对象。对于什么时候会在常量池存储字符串对象,我想我们可以基本得出结论:显示调用String的intern方法直接声明字符串字面常量的时候,例如: String a = "aaa"字符串直接常量相加的时候,例如: String c = "aa" + "bb"; 其中的aa/bb只要有任何一个不是字符串字面常量形式,都不会在常量池生成"aabb"(采用StringBuilder append)

1 背景

1 背景Java String intern经常出现在面试题中,测试两个字符串是不是同一个对象。

对于什么时候会在常量池存储字符串对象,我想我们可以基本得出结论:

  • 显示调用String的intern方法
  • 直接声明字符串字面常量的时候,例如: String a = “aaa”
  • 字符串直接常量相加的时候,例如: String c = “aa” + “bb”; 其中的aa/bb只要有任何一个不是字符串字面常量形式,都不会在常量池生成”aabb”(采用StringBuilder append)

显示调用String的intern方法

直接声明字符串字面常量的时候,例如: String a = “aaa”

字符串直接常量相加的时候,例如: String c = “aa” + “bb”; 其中的aa/bb只要有任何一个不是字符串字面常量形式,都不会在常量池生成”aabb”(采用StringBuilder append)

2 注意事项

2 注意事项2.1 几个对象的问题

2.1 几个对象的问题

1
String str = new String("Hello World");
1
String str = new String("Hello World");

上面的java代码生成几个对象的问题。第二项中在常量池中生成对象的原因是因为”Hello World”在new String init方法中是一个传入的参数,本身是一个字符串字母常量

字符串字母常量1. 堆中字符串对象”Hello World”
2. 字符串常量表中增加”Hello World”常量,如果以前没有的话
3. 执行栈帧中生成本地变量引用str,指向堆中对象

堆中字符串对象”Hello World”

字符串常量表中增加”Hello World”常量,如果以前没有的话

执行栈帧中生成本地变量引用str,指向堆中对象

2.2 字符串变量的+运算

1
public void testStringRef() {        // 此时生成了四个对象 常量池中的"1" + 2个堆中的"1" + s3指向的堆中的对象(注此时常量池不会生成"11")        String s3 = new String("1") + new String("1");        s3.intern();    // jdk1.7之后,常量池不仅仅可以存储对象,还可以存储对象的引用,会直接将s3的地址存储在常量池        String s4 = "11";    // jdk1.7之后,常量池中的地址其实就是s3的地址        System.out.println(s3 == s4); // jdk1.7之前false, jdk1.7之后true        s3 = new String("2") + new String("2");        s4 = "22";        // 常量池中不存在22,所以会新开辟一个存储22对象的常量池地址        s3.intern();    // 常量池22的地址和s3的地址不同        System.out.println(s3 == s4); // false    }
1
public void testStringRef() {
1
// 此时生成了四个对象 常量池中的"1" + 2个堆中的"1" + s3指向的堆中的对象(注此时常量池不会生成"11")
1
String s3 = new String("1") + new String("1");
1
s3.intern();    // jdk1.7之后,常量池不仅仅可以存储对象,还可以存储对象的引用,会直接将s3的地址存储在常量池
1
String s4 = "11";    // jdk1.7之后,常量池中的地址其实就是s3的地址
1
System.out.println(s3 == s4); // jdk1.7之前false, jdk1.7之后true
1
s3 = new String("2") + new String("2");
1
s4 = "22";        // 常量池中不存在22,所以会新开辟一个存储22对象的常量池地址
1
s3.intern();    // 常量池22的地址和s3的地址不同
1
System.out.println(s3 == s4); // false
1
}

跟字符串常量的+运算不一样,new String的+运算会转化为StringBuilder的append调用,比最终调用toString生成栈上的字符串引用。

intern的调用一方面是把调用intern方法的字符串(equals比较确定)放入常量池,如果没有的话;另一方面是返回常量池中字符串对象,如果对调用结果赋值的话。这里需要注意的是JKD 1.7之后,常量池不仅仅可以存储对象,还可以存储对象的引用,会直接将堆中的地址存储在常量池。

JKD 1.7之后,常量池不仅仅可以存储对象,还可以存储对象的引用,会直接将堆中的地址存储在常量池3 代码参考

3 代码参考https://github.com/shidongwa/TestPrj