- new String都是在堆上创建字符串对象。当调用 intern() 方法时,编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。
- 通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
- 常量字符串的“+”操作,编译阶段直接会合成为一个字符串。如string str=”JA”+”VA”,在编译阶段会直接合并成语句String str=”JAVA”,于是会去常量池中查找是否存在”JAVA”,从而进行创建或引用。
- 对于final字段,编译期直接进行了常量替换(而对于非final字段则是在运行期进行赋值处理的)。
final String str1=”ja”;
final String str2=”va”;
String str3=str1+str2;
在编译时,直接替换成了String str3=”ja”+”va”,根据第三条规则,再次替换成String str3=”JAVA” - 常量字符串和变量拼接时(如:String str3=baseStr + “01”;)会调用stringBuilder.append()在堆上创建新的对象。
- JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。
例子:
package util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigInteger; import java.util.Arrays; public class Main20210907{ public static void main(String[] args) { // 一次 fun1(); System.out.println(); fun2(); System.out.println(); fun3(); System.out.println(); fun4(1); System.out.println(); fun5(); } public static void fun1() { System.out.println("fun1"); String s1 = new String("str"); //等价于两句String s0="str";String s1 = new String(s0);第一句作用:常量池中检查,没有,把常量对象"str"放进常量池中;第二句作用:new String必然会在jvm堆中创建了一个实例 s1.intern();//从字符串常量池中查询当前字符串是否存在,存在不作处理 String s2 = "str";//在常量池中已有常量"str",s2拿过来 System.out.println(s1==s2);//所以为false,因为s1为new的新的实例,s2取得常量池中的常量 System.out.println(s1.intern()==s2);//true } public static void fun2() { System.out.println("fun2"); String s1 = new String("a")+new String("bc");//会把常量"a"和"bc"放进常量池,"abc"不会放进常量池 s1.intern();//检查常量池是否有字符串"abc",常量池不存在"abc",所以(jdk1.6将"abc"复制到常量池,jdk1.7将"abc"的引用放入常量池) /** * JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别, * 区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。 * 简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。 */ String s2 = "abc";//检查常量池有字符串"abc",返回它的引用 System.out.println(s1==s2); //true System.out.println(s1.intern()==s2);//true } public static void fun3() { System.out.println("fun3"); String s0="def"; //常量池加入"def" String s1 = new String("d")+new String("ef"); //常量池加入"d"和"ef" s1.intern();//常量池存在"def"(s0),所以不做操作(导致s2与s1不等) String s2 = "def";//常量池存在s0="def" System.out.println(s1==s2); //false } public static void fun4(int flag) { System.out.println("fun4"); String h="h", ij="ij";//"h"和"ij"都加入常量池 String s1 = h+ij;//会调用stringBuilder.append()在堆上创建新的对象”hij“。 // 情况一 if (flag==1) { String s2 = "hij";//检查常量池 不存在,于是加入“hij” System.out.println(s1==s2); //false }else { s1.intern();//检查常量池不存在"hij",所以(jdk1.6将"hij"复制到常量池,jdk1.7将"hij"的引用放入常量池) String s2 = "hij";//检查常量池,存在“hij” System.out.println(s1==s2); //true } } public static void fun5() { System.out.println("fun5"); final String k="k", lm="lm";//"k"和"lm"都加入常量池(final修饰) String s1 = k+lm;//直接替换成了String s1=”k”+”lm”,再根据常量字符串的“+”操作,编译阶段直接会合成为一个字符串。所以再次替换为String s1="klm","klm"也纳入常量池; String s2 = "klm";//检查常量池,存在“klm” System.out.println(s1==s2); //true } }