引言
在学习《深入理解Java虚拟机》的书时,又看到了2.4.3(P63)的 String.intern()返回引用的测试。引发了一些思考。String.intern()具体做了什么事情,以及和字符串常量池有什么关系,这里做一下自己的学习记录。
结论先行
1.7之前。调用intern方法返回的对象 和 new出来的对象 用
==
判断是不会相等的,因为一个在永久代一个在堆中。1.7以及之后,就要看在new之前常量池中是否已经存在了,具体的通过
==
判断是否相等就分两种情况了,具体的看下面的案例中的解析,非常详细。
前置知识
1.7之前,字符串常量池是放在方法区(永久代)中的,这块是无法触发FGC的。intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,这个时候是一个新的实例对象,方法返回的也是永久代中这个对象的引用。
在1.7以及以后,字符串常量池放到了堆中,同时永久代换成了元空间。因为字符串常量池和对象都是在堆上的,所以常量池中保存的可能是对象也可能是堆上已经存在的对象的引用。当调用str.intern()方法的时候,如果常量池中已经存在,则返回常量池中的对象(或者引用);如果常量池不存在,则会在常量池中添加常量,并且这个常量指向堆中的str这个对象。
案例解析
案例一
验证常量池中不存在的时候,intern会放入常量池,并且指向堆中的这个字符串对象的引用
public static void main(String[] args) {
/**
* 基于 JDK8
*
* 验证常量池中不存在的时候,intern会放入常量池,并且指向堆中的这个字符串对象的引用
*
* ***************Heap*************************
*
* Object01("计算机软件") 步骤一 str1那里new出来的
*
* Object02("计算机软件") 步骤三 str3那里new出来的
*
* ***************Heap*************************
*
*
* *************String Pool********************
*
* 指向Object01("计算机软件")的引用 步骤二 str1.intern()生成的,因为这个时候常量池没有这个常量
*
* *************String Pool********************
*
*
* *************解析****************************
*
* 第一步在堆里new了一个对象 Obj1
* 第二步intern操作因为常量池中还没有,所以常量池放了一个字符串常量,它实际指向了Obj1
* 此时 str1 str2都是指 的堆中的那个对象Obj1
*
* 第三步在堆里new了一个新对象 Obj2
* 第四步 str4也指向了这个对象Obj2
* 此时 str3 str4 都是指向 Obj2
*
* 第五步intern操作,常量池已经存在了这个字符串常量,所以直接取得值,这个值是指向Obj1的
* 此时 str5 是指向Obj1这个对象的
*
* 到此 str1 str2(指向常量池,常量池又指向Obj1) str5(指向常量池,常量池又指向Obj1) 都是指向 Obj1
* str3 str4 指向Obj2
*
*
* *************解析****************************
*/
String str1 = new StringBuilder("计算机").append("软件").toString(); // 1
String str2 = str1.intern(); //2
String str3 = new StringBuilder("计算机").append("软件").toString(); //3
String str4 = str3; //4
String str5 = str3.intern(); //5
// true
System.out.println(str1 == str2);
// false
System.out.println(str3 == str1);
// true
System.out.println(str5 == str2);
// true
System.out.println(str4 == str3);
// false
System.out.println(str5 == str3);
// false
System.out.println(str4 == str2);
// true
System.out.println(str5 == str1);
}
案例二
验证 常量池中先存在对象的时候的时候,intern()返回的是常量池中的对象,并不会指向堆中的对象的引用
public static void main(String[] args) {
/**
*
* 基于JDK8
*
* 验证常量池中先存在的时候,intern()返回的是常量池中的对象
*
* ***************Heap*************************
*
* Object01("计算机软件") 步骤一 str1那里new出来的
*
* Object02("计算机软件") 步骤三 str3那里new出来的
*
* ***************Heap*************************
*
*
* *************String Pool********************
*
*
* 字符串常量 "计算机软件" 步骤0 编译时把这个字符串常量放入常量池
*
* *************String Pool********************
*
*
* *************解析****************************
*
* 第0步在常量池中放入一个字符串常量 "计算机软件"
* 此时str指向这个常量
*
* 第一步在堆里new了一个对象 Obj1
* 此时 str1 指向Obj1
*
* 第二步intern操作因为常量池中已存在,所以此时str2指向常量池中的这个字符串
*
* 第三步在堆里new了一个新对象 Obj2 此时 str3 指向Obj2
* 第四步 str4也指向了这个对象Obj2
* 此时 str3 str4 都是指向 Obj2
*
* 第五步intern操作,常量池已经存在了这个字符串常量,所以str5 直接指向常量池中的这个字符串
*
* 到此 str1 都是指向 Obj1
* str2 str5 指向的是常量池中的常量
* str3 str4 指向Obj2
*
*
* *************解析****************************
*/
String str = "计算机软件"; //0
String str1 = new StringBuilder("计算机").append("软件").toString(); // 1
String str2 = str1.intern(); //2
String str3 = new StringBuilder("计算机").append("软件").toString(); //3
String str4 = str3; //4
String str5 = str3.intern(); //5
// true
System.out.println(str5 == str2);
// true
System.out.println(str3 == str4);
// false
System.out.println(str1 == str2);
// false
System.out.println(str1 == str5);
// false
System.out.println(str3 == str1);
// false
System.out.println(str1 == str4);
// false
System.out.println(str3 == str2);
// false
System.out.println(str4 == str2);
}
案例三
验证,当字符串常量池中不存在的时候,谁先调用intern()方法,常量池中的这个引用就指向堆中的那个对象
public static void main(String[] args) {
/**
*
* 基于JDK8
*
* 验证在常量池中不存在的时候,谁先调用intern()方法,常量池中的常量就会指向谁
*
* ***************Heap*************************
*
* Object01("计算机软件") 步骤一 str1那里new出来的
*
* Object02("计算机软件") 步骤三 str3那里new出来的
*
* ***************Heap*************************
*
*
* *************String Pool********************
*
* 指向Object02("计算机软件")的引用 步骤五 intern()方法放入常量池,并指向堆中的对象Object02("计算机软件")
*
* *************String Pool********************
*
* *************解析****************************
*
* 第一步在堆里new了一个对象 Obj1
* 此时 str1 指向Obj1
*
*
* 第三步在堆里new了一个新对象 Obj2 此时 str3 指向Obj2
* 第四步 str4也指向了这个对象Obj2
* 此时 str3 str4 都是指向 Obj2
*
* 第五步intern操作因为常量池中还没有,所以常量池放了一个字符串常量,它实际指向了Obj2(和str3指向相同)
* 第二步intern操作因为常量池中已存在,所以此时str2指向常量池中的这个字符串的引用,实际指向的Obj2
*
* 到此 str1 都是指向 Obj1
* str2 str3 str4 str5 都实际指向Obj2
*
* *************解析****************************
*/
String str1 = new StringBuilder("计算机").append("软件").toString(); // 1
String str3 = new StringBuilder("计算机").append("软件").toString(); //3
String str4 = str3; //4
String str5 = str3.intern(); //5
String str2 = str1.intern(); //2
// true
System.out.println(str5 == str2);
// true
System.out.println(str3 == str2);
// false
System.out.println(str1 == str2);
// true
System.out.println(str4 == str2);
// false
System.out.println(str3 == str1);
// false
System.out.println(str1 == str4);
// false
System.out.println(str5 == str1);
// true
System.out.println(str4 == str5);
}