- 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
}
}