参考下面代码和说明:
@Test
public void test() {
/**
* String str1= “abc”; 在编译期,JVM会去常量池来查找是否存在“abc”,
* 如果不存在,就在常量池中开辟一个空间来存储“abc”;
* 如果存在,就不用新开辟空间。然后在栈内存中开辟一个名字为str1的空间,来存储“abc”在常量池中的地址值。
*
* String str2 = new String("abc") ;
* 在编译阶段JVM先去常量池中查找是否存在“abc”,
* 如果过不存在,则在常量池中开辟一个空间存储“abc”。
* 在运行时期,通过String类的构造器在堆内存中new了一个空间,
* 然后将String池中的“abc”复制一份存放到该堆空间中,
* 在栈中开辟名字为str2的空间,存放堆中new出来的这个String对象的地址值。
* 不论new String这种方式在常量池是否创建对象,它指向的始终是堆中的对象。
* 堆中对象指向字符串常量池中的”abc“/或者说堆中对象又存有字符串常量池中"abc"的引用地址
*/
String s1 = "Programming";
String s2 = new String("Programming");
String s3 = "Program";
String s4 = "ming";
/**
* 常量加常量
* 编译器自动优化成 "Programming";
*/
String s5 = "Program" + "ming";
/**
* 变量加变量==变量加常量
* 变量加常量会new一个Stringbuilder并且调用Stringbuilder.appen方法将他们拼接在一起
* String s6 = s3 + s4;
* 等价于String s6 = s3 + "ming";String s6 = "Program" + s4;
* 等价于
* StringBuilder s = new StringBuilder();
* s.append("Program");
* s.append("ming");
* String str = stringB.toString();
* StringBuilder.toString 等价于 new String() 括号里是StringBuilder的值"Programming"
*/
String s6 = s3 + s4;
StringBuilder s7 = new StringBuilder();
s7.append(s3);
s7.append(s4);
String s8 = s7.toString();
System.out.println(s6 == s8);//false
/**
* 通过字面量赋值创建字符串(如:String s=”string”)时,会先在常量池中查找是否存在相同的字符串,
* 若存在,则将栈中的引用直接指向该字符串;
* 若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
*
*/
System.out.println(s1 == s2);//false
System.out.println(s1 == s5);//true
System.out.println(s1 == s6);//false
System.out.println(s1 == s6.intern());//true
/**
* s2在堆中
* s2.intern()在字符串常量池中
* new String()都是在堆上创建字符串对象。当调用 intern() 方法时,编译器会将字符串添加到常量池中并返回指向该常量的引用。
*/
System.out.println(s2 == s2.intern());//false
/**
* String s12 = s9 + s10;等价于 "a"+"b"
* 编译期优化后 直接赋值给s12 = "ab"
*
* 字符串拼接操作不一定使用的是stringBuilder!
* 如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非stringBuilder的方式。
*
* 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
* 在linking准备阶段就对静态变量进行默认值初始化,如果是有final修饰的就显示初始化了
*/
final String s9 = "a";
final String s10 = "b";
String s11 = "ab";
String s12 = s9 + s10;
String s13 = s9 + "b";
System.out.println(s11 == s12);//true
System.out.println(s11 == s13);//true
}
String.intern()是一个Native方法,底层调用C++的 StringTable::intern方法实现。当通过语句str.intern()调用intern()方法后,JVM 就会在当前类的常量池中查找是否存在与str等值的String,若存在则直接返回常量池中相应Strnig的引用;若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。因此,只要是等值的String对象,使用intern()方法返回的都是常量池中同一个String引用,所以,这些等值的String对象通过intern()后使用==是可以匹配的。
变量加变量==变量加常量 字节码指令说明图
注意
字符串拼接操作不一定使用的是StringBuilder
如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。在linking准备阶段就对静态变量进行默认值初始化,如果有final就显示初始化了。如上述代码中s12的示例