有趣的有趣的String和intern()
方法
在JDK1.7及以后的版本里,intern()方法会在常量池中记录首次出现的实例引用,然后返回该引用。
String的声明与常量池
1 | public class Test1 { |
分析
字符串不属于基本类型,但是可以像基本类型一样,直接通过字面量赋值,当然也可以通过new来生成一个字符串对象。不过通过字面量赋值的方式和new的方式生成字符串有本质的区别:
- 通过字面量赋值创建字符串时,会优先在常量池中查找是否已经存在相同的字符串,倘若已经存在,栈中的引用直接指向该字符串;倘若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
- 而通过new的方式创建字符串时,就直接在堆中生成一个字符串的对象(备注,JDK 7 以后,HotSpot已将常量池从永久代转移到了堆中。栈中的引用指向该对象。对于堆中的字符串对象,可以通过 intern() 方法来将字符串添加的常量池中,并返回指向该常量的引用。
- 因为str1指向的是字符串中的常量,str2是在堆中生成的对象,所以str1==str2返回false。
- str2调用intern方法,会将str2中值(“string”)复制到常量池中,但是常量池中已经存在该字符串(即str1指向的字符串),所以直接返回该字符串的引用,因此str1==str2返回true。
“final”修饰符 与 “+”操作符
1 | public class Test2 { |
反编译
- 常量池
1 | Constant pool: |
- 方法栈
介绍两条指令:
- ldc:Push item from run-time constant pool,从常量池中加载指定项的引用到栈。
- astore_
:Store reference into local variable,将引用赋值给第n个局部变量。
1 | public static void main(java.lang.String[]); |
- 对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的。所以final String 字段等同于常量赋值。
- 对于
+
操作符,编译器将其转化为StringBuilder.append
来实现字符串的拼接。
JDK1.6 和JDK1.7的区别
1 | public class InternTest { |
1 | public class InternTest01 { |
- output
- JDK1.6
- false
- false
- JDK1.7
- true
- false
- JDK1.6
分析
根据对代码段一的分析,应该可以很简单得出 JDK 1.6 的结果,因为 str2 和 str1本来就是指向不同的位置,理应返回false。
比较奇怪的问题在于JDK 1.7后,对于第一种情况返回true,但是调换了一下位置返回的结果就变成了false。这个原因主要是从JDK 1.7后,HotSpot 将常量池从永久代移到了元空间,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。所以:
结果 #7:在第一种情况下,因为常量池中没有“str01”这个字符串,所以会在常量池中生成一个对堆中的“str01”的引用,而在进行字面量赋值的时候,常量池中已经存在,所以直接返回该引用即可,因此str1和str2都指向堆中的字符串,返回true。
结果 #8:调换位置以后,因为在进行字面量赋值(String str1 = “str01”)的时候,常量池中不存在,所以str1指向的常量池中的位置,而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。