从JDK7开始,字符串常量池被移到了堆区中,因此Java程序中的字符串常量对象要么在堆区的字符串常量池之中,要么在堆区的字符串常量池之外。为了做区分,下文将堆区的字符串常量池区域称为字符串常量池,将堆区字符串常量池之外的区域称为堆区。
一.使用字面量直接赋值
String str = "abc";
1.使用这种方式可以保证字符串常量池中一定有字符串对象"abc"。
2.执行步骤:
(1)通过值比较的方式判断字符串常量池中是否有字符串对象"abc":
(2)若有,则直接将该对象的引用返回给str;
(3)若没有,则先在字符串常量池中创建字符串对象"abc",再将其引用返回给str。
3.特征:
(1)字符串常量池一定会有声明的字面量的字符串对象。
(2)创建对象的个数:[0,1]:
a.只有当字符串常量池没有"abc"对象,才会创建。即字符串常量池中的对象具有唯一性。
b.全程与堆区无关。
(3)返回的是指向字符串常量池中的引用。
二.使用new String(字面量)
String str = new String("abc");
1.使用这种方式可以保证字符串常量池和堆区都会有字符串对象"abc"。
2.执行步骤:
(1)通过值比较的方式判断字符串常量池中是否有字符串对象"abc":
(2)若有,则继续执行;
(3)若没有,则会在字符串常量池中创建字符串对象"abc";
(4)在堆区中创建字符串对象"abc",并将其引用返回。
3.特征:
(1)字符串常量池和堆中都一定会有声明的字面量的字符串对象。
(2)创建对象的个数:[1,2]。
a.只有当字符串常量池没有"abc"对象,才会创建。即字符串常量池中的对象具有唯一性。
b.一定会在堆区创建一个"abc"对象。因此堆区可能会有多个"abc"对象,每执行一次new String("abc")就会创建一个"abc"对象。
(4)返回的是指向堆中的引用。
三.使用new String(变量)
String str = new String(str1);
1.这种方式只会在堆中创建字符串对象。
2.解析:
(1)字符串常量池的来源链是:字节码文件的静态常量池——>静态常量池中的所有字符串常量——>堆中的字符串常量池。
(2)即字符串常量池中的数据都是在编译阶段确定的。像上述这种创建字符串对象的方式,由于涉及到变量,因此需要在在运行时动态构建,无法加入字符串常量池。
(3)str指向堆中的对象。
四.使用StringBuilder
String str = new StringBuilder().append("a").append("b").toString();
1.对于作为方法参数传递的字面量"a"、"b",因为其已经显示声明,在编译阶段是可知的,因此字符串常量池中会加入对象"a"、"b"。
(1)上述对显示声明的字面量字符串对象作为参数传递,可以拆解成以下步骤
String stra = "a";
String strb = "b";
String str = new StringBuilder().append(stra).append(strb).toString();
也就是说字符串对象"a"和"b"会被加入到字符串常量池中,但不会在堆中创建对象。
(2)由此可知,使用字面量作为参数传递时,该字面量也会被作为字符串对象加入到字符串常量池中。
2.上述语句的执行结果是str = "ab";但同样地,由于涉及到类的调用和方法的执行,因此对字符串对象"ab"是在运行时动态构建的,其只能在堆中创建对象,无法加入到字符串常量池中。
3.str指向堆中的对象。
4.验证:使用jclasslib查看编译成的字节码文件中的静态常量池
可以看到,该类的静态常量池中唯二的两个字符串常量就是"a"和"b",它们会被作为字符串常量对象加入到字符串常量池中,而没有"ab"。
五.使用"+"连接
1.等号右边全为字面量
String str = "a" + "b";
(1)在编译阶段,编译器会对全由字面量构成的连接时进行优化,称为常量折叠。优化结果为
String str = "ab";
(2)字符串"a"和"b"由于常量折叠而丢失,不会创建任何对象。最终会为优化后得到的字符串"ab"在字符串常量池中创建对象。
(3)str指向字符串常量池中的对象
(4)验证:
可以看到,静态常量池中只有一个字符串常量"ab"。
2.等号右边至少含有一个变量
String str = str1 + "a" + "b";
(1)只要涉及到对变量的操作,就只能在运行时动态构建。
(2)上述语句会在字符串常量池中创建字符串对象"a"和"b",会在堆中创建字符串对象作为动态执行的结果。
(3)str指向堆中的对象。
六.总结
1.对于代码中所有的字面量字符串,无论是显式声明用于构建String对象还是用于传参,只要不被常量折叠,就一定会为其在字符串常量池中创建对象。
2.对于通过调用类、变量、方法等方式动态构建的字符串,由于需要运行时动态构建,因此无法加入字符串常量池,只能在堆中创建对象。
七.String类提供的intern()方法
intern()方法会将字符串常量对象放到字符串常量池中,并返回其引用。对于不同的JDK版本有不同的实现:
///str是一个在堆中的字符串对象
System.out.println(str.intern() == str);
1.JDK6及之前的intern()方法:
(1)如果str在字符串常量池中,则返回其位于字符串常量池中的引用。输出结果为false。
(2)如果str不在字符串常量池中,则在字符串常量池中创建一个str并返回其引用。输出结果为false。
(3)str是对堆中的引用,而str.intern()只会返回字符串常量池中的引用,因此结果一定是false。
2.JDK7及之后的方法:
(1)如果str在字符串常量池中,则返回其位于字符串常量池中的引用。输出结果为false。
(2)如果str不在字符串常量池中,则在字符串常量池中存入对堆中str的引用,并将该引用返回,而不会再创建一个对象。输出结果为true。
(3)str是对堆中的引用,而str.intern()的返回结果有两种可能:对字符串常量池中的引用、对字符串常量池的引用——>对堆中对象的引用。