一、官网对字符串字面量的一些解释
如图
网址来源:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.1
1、从中可获取的信息
- 首先注意,字符串字面量
"abc"
与字符串对象(String类对象)new String("abc")
不一样。(这里是个人理解) - 一个字符串字面量是一个String类对象的引用。
- java程序要求完全相同的字符串字面量必须引用相同的String类对象。
- 完全相同的意思是字面量包含相同的code points(原文是
that is, literals that contain the same sequence of code points
)。 - 这里的code points的我理解的意思是每一个code point对应unicode中的一个字符,code points代表多个unicode中的字符。(参考:java中codepoint是什么?)
- 完全相同的意思是字面量包含相同的code points(原文是
- 如果String的
intern()
方法被调用在任意字符串上的话,且如果该字符串是一个字面量,那么返回的结果都是对于同一个类对象的引用。 - 以下是对上一点的解释(也是从中获取的信息):
- 为了得到字符串字面量,JVM会检查
CONSTANT_string_info
结构给出的code points:- 如果String的
intern()
方法以前被一个包含相同的Unicode code points序列的String对象(该对象的创建过程使用到了相同的CONSTANT_String_info
)调用过的话,那么后面的字符串字面量的引用由来都是这个字符串对象的引用。(详情看代码示例1) - 否则,一个新的String类对象会被创建,而这个新的String类对象所包含的Unicode code points,是从字节码的常量池的
CONSTANT_String_info
的结构中获取的。然后,一个该类对象的引用就会给那个字符串字面量,最后,调用一下新的String对象的intern()
方法。
- 如果String的
- 为了得到字符串字面量,JVM会检查
2、我对以上官方文档的理解
- 当字符串字面量出现的时候,如果在之前存在一个字符串对象中的字符串的值和这个字符串字面量的值一样的话,而且之前的那个字符串对象还调用过String的
intern()
方法的话,那么这个刚出现的字符串字面量获得的引用就是这个字符串对象的引用。 - 当字符串字面量出现的时候,如果字符串常量池中不存在一个引用,该引用所引用的字符串对象的字符串的值和这个字符串字面量的值一样的话,就会创建一个新的字符串对象,并返回该新字符串对象的引用给那个字符串字面量,然后调用新的字符串对象的
intern()
方法,这个引用还会存放到字符串常量池中。
二、我对java字符串对象引用的理解
我这里是基于jdk8
的HotSpot
虚拟机的理解。以上的文档也是oracle的jdk8官网中的。
- 字符串对象存在在堆中,字符串常量池(StringTable)放在堆中,字符串常量池保存字符串的引用。
- PS:我在官方文档中找了一会儿没找到字符串常量池存放在堆中的证据,但是网上和我学到的都是字符串常量池放在堆中,因此我也默认字符串常量池放在堆中。也没找到有字符串常量池这一概念…于是我的理解就是字符串常量池存放的引用所引用的字符串对象中的字符串值全都不一样,而且只有当字符串对象调用了
intern()
方法后,且字符串常量池中的所有引用引用的字符串对象的字符串值和当前这个调用intern()
方法的字符串对象的字符串值不一样的时候,才会把当前这个调用intern()
方法的字符串对象的引用放进去。字符串常量池就是存放这些的。
- PS:我在官方文档中找了一会儿没找到字符串常量池存放在堆中的证据,但是网上和我学到的都是字符串常量池放在堆中,因此我也默认字符串常量池放在堆中。也没找到有字符串常量池这一概念…于是我的理解就是字符串常量池存放的引用所引用的字符串对象中的字符串值全都不一样,而且只有当字符串对象调用了
- 当出现第一次出现字符串字面量的时候,才会创建一个新的字符串对象并把该字符串对象引用放入到字符串常量池中。示例如下(
"abc"
均是第一次出现):String str = "abc";
String str = new String("abc");
- 注意,这里是
"abc"
字符串字面量所代表的引用(该引用还是指向一个字符串对象)放到了字符串常量池中,而不是str
这个字符串对象的引用放到了字符串常量池中。
- 注意,这里是
- 如果是
Striing str = new String("xx") + new String("xx")
这样创建字符串对象的情况,因为实际上调用的是StringBuilder对象的append(String str)
方法,拼接完后使用StringBuilder对象的toString()
方法,而这个方法调用的却是new String(value, 0, count)
,这个方法的参数列表是public String(char value[], int offset, int count)
,传入的是一个char数组,并不是传入一个字符串字面量,因为并没有出现一个新的字符串对象,因此并没有创建一个新的字符串对象。(可以查看源码并debug一下,我上一篇博客讲过了)
三、代码示例
1、代码示例1
- 代码如下:
public class StringTest01 {
public static void main(String[] args) {
char[] cha1 = new char[2];
cha1[0] = 'a';
cha1[1] = 'b';
// 当前没有出现过"ab"字符串字面量
// 以下调用的是String(char[] value[])
String str1 = new String(cha1);
System.out.println(str1.intern());
String str2 = "ab";
System.out.println(str1 == str2);
}
}
- 运行结果是:
ab
true
而如果注释掉倒数第二行再运行结果是false
。
1、代码及运行结果解释
注意,这里new String对象用的构造方法是String(char[] value)
,在最后一行之前没有出现过"ab"
的字符串字面量。
这个例子可以说明,在还未出现字符串字面量"ab"
之前,如果创建的字符串对象中使用到了intern()
方法,那么当再出现字符串字面量"ab"
的时候,它的引用就是之前创建的那个字符串对象。
that given by the CONSTANT_String_info structure
(我觉得这里想表达的意思是字符串对象里的char[]数组存放的字符串与CONSTANT_String_info
中的一样,CONSTANT_String_info
是字节码常量池中的结构)。
再来解释下那个CONSTANT_String_info
是什么,如图,这是jclasslib插件查看StringTest01类的字节码常量池:
这个常量池的信息的意思是一个引用,指向了第42个常量信息:
这就是CONSTANT_String_info
的结构。
再通过查看字节码指令可知,#7
这个常量池中的常量信息被使用是在赋值给str2
变量的时候。
2、代码示例2
- 代码如下:
public class StringTest {
public static void main(String[] args) {
String str1 = new String("1") + new String("2");
str1.intern();
System.out.println(str1 == "12");
String str2 = "3" + new String("4");
System.out.println(str2 == "34");
String str3 = new String("56");
System.out.println(str3 == "56");
String str4 = new String("78");
System.out.println(str4.intern() == "78");
}
}
- 运行结果:
true
false
false
true
2、代码及运行结果解释
- 首先我先通过idea的jclasslib插件查看字节码指令,发现当涉及到
new String("xxx")
和+
操作同时使用的时候,都会先new一个StringBuilder对象再调用append(String str)
方法最后再调用StringBuilder对象的toString()
方法。其他没设计到new String("xxx");
和+
同时使用的时候,就不会new一个StringBuilder对象。 - 解释第一个
true
:- 根据前面的我的理解以及官网解释可知,本来str1这个字符串对象的引用并没有存放在字符串常量池中的(str1没调用过
intern()
),但是因为str1.intern()
,因此str1的引用存放到字符串常量池中了,然后比较的时候(这里出现的"12"
是第一次出现的"12"
字符串字面量,它得到的是str1的引用)就会一样。
- 根据前面的我的理解以及官网解释可知,本来str1这个字符串对象的引用并没有存放在字符串常量池中的(str1没调用过
- 解释第二个的
false
:- 因为str2没调用过
intern()
,于是字符串常量池并没有str2的引用,当"34"
字符串字面量第一次出现的时候,就会创建新的字符串对象并把引用放入到字符串常量池中,于是比较的时候不一样。
- 因为str2没调用过
- 解释第三个的
false
:- 通过new出来的字符串对象为什么就没有把引用放入到字符串常量池中呢?是为因为没有调用
intern()
吗?我觉得并不是这个原因,而是因为这种创建字符串对象方式调用的字符串类对象构造方法的参数列表是public String(String original)
,也就是说字符串字面量已经出现了,就是那个传入的参数"56"
,于是字符串常量池中的引用其实就是那个"56"
字符串字面量的引用。
- 通过new出来的字符串对象为什么就没有把引用放入到字符串常量池中呢?是为因为没有调用
- 解释第四个的
true
:intern()
方法获得的是在字符串常量池中的引用,如果没有,就把当前调用该方法的字符串对象的引用放进去。可以看出,在创建str4之前,已经出现了"78"
这个字符串字面量了,此时才是真正创建第一个字符串值是"78"
的字符串对象的时候。于是最后一行是true。
四、补充:String的intern()
方法
之前在写这篇博客的时候,居然没有去看这个方法的api文档解释,于是现在我补充一下:
1、api原文
2、我的理解
- 从这个api的解释可以看出,的确是存在着一个字符串池
a pool of strings
,这个字符串池最初是空的,由类String单独维护。 - 当调用
intern
方法时,如果池中已经包含一个引用所引用的字符串对象与当前调用intern()
方法的String对象在使用了equals(object)
方法后确定是完全一样的话,则返回池中的字符串引用。否则,此字符串对象引用将添加到池中,并返回对此字符串对象的引用。(原文并没有出现“引用”,而是说把字符串对象放到池中,但因为我之前debug一个字符串对象使用了intern
方法后地址并没有变,因此我认为这里的把字符串对象放到池中是放入字符串对象的引用) - 因此,对于任何两个字符串
s
和t
,s.intern() == t.intern()
为true
的,当且仅当s.equals(t)
为true
时。 All literal strings and string-valued constant expressions are interned.
这句话我的理解是:所有字符串字面量和字符串值常量表达式都是已经用过intern()
方法的。- 返回一个字符串对象引用,该引用所指向的字符串对象的内容和当前调用该
intern()
方法的字符串对象的内容相同,保证来自唯一的字符串池。