1. intern()方法简介
如果字符串s在字符串常量池中存在对应字面量,则intern()方法返回该字面量的地址;如果不存在,则创建一个对应的字面量,并返回该字面量的地址
2. String对象与字面量的intern()区别
public static void main(String[] args) {
String s1 = new String("字符串");
String s2 = "字符串";
System.out.println(s2 == s2.intern());
System.out.println(s1 == s1.intern());
System.out.println(s1.intern() == s2.intern());
}
结果是True / False / True,解释如下:
3. 字符串加法与intern()
public static void test04(){
String s1 = "今天吃" + "炒饭";
String s2 = new String("今天吃") + new String("炒饭");
String s3 = new String("今天吃") + "炒饭";
System.out.println(s1.intern() == s1);
System.out.println(s2 == s2.intern());
System.out.println(s3 == s3.intern());
}
//对应的.class字节码文件
public static void test04() {
String s1 = "今天吃炒饭";
String var10000 = new String("今天吃");
String s2 = var10000 + new String("炒饭");
var10000 = new String("今天吃");
String s3 = var10000 + "炒饭";
System.out.println(s1.intern() == s1);
System.out.println(s2 == s2.intern());
System.out.println(s3 == s3.intern());
}
返回结果为true / false / false,解析如下:
如果字符串加法中全是字面量相加,则在前端编译为.class字节码文件时,会被直接优化为加和,例如s1在.class文件中被优化为:s1 = "今天吃炒饭",因此s1在被后端编译时,编译器/解释器是看不到相加的过程的,它等效于直接对s1以字面量赋值,所以s1 == s1.intern()
如果字符串加法中存在String对象,前端编译不会将其优化为字面量。关于这个过程,有视频/博客说用的是StringBuilder来做的相加,即new StringBuilder("今天吃").append("炒饭").toString()完成的调用。但是查看字节码文件后,发现新版的JDK不是使用此方式完成的。JDK15中,字符串相加的字节码指令为:
invokedynamic #61 <makeConcatWithConstants, BootstrapMethods #0>
它调用了一个makeConcatWithConstants的方法,此方法位于StringConcatFactory类中,JDK对该方法的描述为:
This strategy replicates what StringBuilders are doing: it builds the
* byte[] array on its own and passes that byte[] array to String
* constructor. This strategy requires access to some private APIs in JDK,
* most notably, the private String constructor that accepts byte[] arrays
* without copying.
即底层是:拼接得到一个byte[]数组,然后传递给一个String的构造函数得到拼接结果。重点在于其构造函数的参数是byte[],而不是String。因此单独看下方这一行代码,是不会出现"今天吃炒饭"这个字符串的,故字符串常量池中不会出现"今天吃炒饭"这个字面量。则调用s2.intern()方法时,会将s2的地址拷贝一份放到常量池中,然后返回s2的地址,故s2 == s2.intern()。
String s2 = new String("今天吃") + new String("炒饭");
但是这跟我们的结果不一样,结果中得到的是false,这个一个小坑。因为s1中我们令s1 = "今天吃" + "炒饭",已经拼出了一个"今天吃炒饭"字面量,这才导致s1.intern()已经在常量池中得到了字面量。如果我们去掉第一行:
public static void test04(){
String s2 = new String("今天吃") + new String("炒饭");
System.out.println(s2 == s2.intern());
}
得到的结果就是true
4. StringBuilder与intern()
StringBuilder中维护着一个可变的byte[]用于存储数据,其append(String s)方法是为byte[]数组添加上一些数据,并更新byte[]的有效长度。因此在append过程中,是不会有任何新的字符串被产生的。仅当StringBuilder对象调用toString方法时,才会返回一个new出来的字符串。因此append()过程中,不会有任何中间值出现,例如String s1 = "a".append("b").append("c"),是不会出现"ab"这个字符串的,因为只有调用toString()方法后,才会真正得到字符串对象,其余都是byte[]数组
因此考察如下代码:
public void test01(){
String s1 = new StringBuffer("计算机").append("软件").toString();
System.out.println(s1==s1.intern());
System.out.println("计算机软件" == s1);
}
打印true / true,解析如下:
而对于以下代码:
String s1 = new StringBuilder("字符串").toString();
System.out.println(s1.intern() == s1);
System.out.println(s1 == "字符串".intern());
得到的结果为false / false,原因如下:
在new StringBuilder的过程中,使用到了"字符串"这个字面量,因此new StringBuilder("字符串")已经在常量池中创建了"字符串"字面量。调用toString方法返回堆空间的地址。由于"字符串"字面量已经存在于常量池中,故s1.intern()返回其地址;而s1为堆空间中String对象的地址,二者显然不同。第二个判断也同理