1 String 对象的两种创建方式
1.1 字面量
String str1 = "abcd";
字面量方式会创建0或1个对象:
- 如果String Pool 中没有 “abcd” 字符串对象,编译期会在String Pool中创建一个“abcd”字符串对象,运行期返回String Pool 中该“abcd”字符串对象的引用;
- 如果String Pool 中已经有一个 “abcd” 字符串对象,则编译期不创建新的"abcd" 字符串对象,运行期直接返回String Pool 中该 “abcd” 字符串对象的引用;
String s1 = "abcd";
String s2 = "abcd";
System.out.println(s1 == s2); // true
1.2 new
String str2 = new String("abcd");
new方式会创建1或2个字符串对象:
- 如果String Pool 中没有 “abcd” 字符串对象,编译期会在String Pool中创建一个"abcd"字符串对象;如果String Pool 中已经有一个 “abcd” 字符串对象,则编译期不创建新的"abcd" 字符串对象;
- 运行期使用 new 的方式在堆中创建另外一个"abcd"字符串对象 ,并返回该字符串对象的引用;
String s1 = new String("abcd");
String s2 = new String("abcd");
System.out.println(s1 == s2); // false
2 intern() 方法
- 字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定;
- 不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中;
- 当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定), 那么就会返回 String Pool 中字符串的引用; 否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用;
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。 而在 Java 8以后,String Pool被移到堆中,这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
3 字符串拼接
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的对象
String str4 = str1 + str2; //TODO:在堆上创建的新的对象
String str5 = "string";//常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
注意:尽量避免多个字符串拼接,因为这样会重新创建对象。 如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
public class Test {
public static void main(String[] args) {
//(1)
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); // false
//(2)
String a1 = "ab";
final String bb1 = "b";
String b1 = "a" + bb1;
System.out.println((a1 == b1)); // true
//(3)
String a2 = "ab";
final String bb2 = getBB();
String b2 = "a" + bb2;
System.out.println((a2 == b2)); // false
}
private static String getBB(){
return "b";
}
}
- JVM对于字符串引用,由于在字符串的”+”连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即”a” + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。
- (2)和(1)中唯一不同的是bb1字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的”a” + bb1和”a” + “b”效果是一样的。故上面程序的结果为true。
- JVM对于字符串引用bb2,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和”a”来动态连接并分配地址为b2,故上面程序的结果为false。