String 类的 intern 方法非常特殊,底层做了很多操作,面试中也经常被问到,本文将带大家深度剖析此方法。
在正式开始之前,需要你知道 String 内存布局:
变量始终存的都是地址,只是是堆中地址还是常量池地址的问题。JVM 规范说明:方法区逻辑上属于堆内存,具体放在哪以及是否做垃圾回收由具体实现决定。
有了上面的铺垫,下面开始正文。
一、 方法注释
无论是 1.6 还是 1.8 关于 intern 方法的注释都是一样的:
简单一句话:调用 intern 方法时,会首先到常量池中找具有相等值的对象,找到的话返回常量池的对象,否则将该对象加入常量池,并返回该对象的引用。
有个大概印象,下面通过代码来加深理解。
下面先看下面代码:
String str1 = new String("计算机软件");
String intern = str1.intern();
System.out.println(str1 == intern);
返回的是 false,为什么呢,我们用 javap 生成汇编代码:
可以看到 new String 使用的字符串已经放入常量池里了, str1 指向的是堆里对象实例的地址(new 申请的堆空间),str1.intern 会首先到常量池检查是否有相等(equals)的字符串,很显然有, 返回该常量池中字符串对应的地址。
所以 str1 指向的是堆里的地址,str1.intern 返回的事常量池中的地址,因此返回 false。
很显然,下面代码中的结果就是 true, 因为都是常量池的地址。
String str1 = "计算机软件";
String intern = str1.intern();
System.out.println(str1 == intern);
下面,我们把代码稍微修改下:
String str1 = new StringBuilder("计算机").append("软件").toString();
String intern = str1.intern();
System.out.println(str1 == intern);
这个时候结果是 true。还是使用 javap 反编译:
大家发现没有,连接后的字符串 “计算机软件”并没有加入到常量池中,仍然在堆中,因此 intern 会将该对象(引用)加入常量池,并返回常量池里的该引用。
所以 str1 和 str1.intern 指向的都是是堆里的地址,因此返回 true。
比较绕,我们再看下面的代码:
String str = "计算机软件";
String str1 = new StringBuilder("计算机").append("软件").toString();
String intern = str1.intern();
System.out.println(str == intern); // true
System.out.println(str1 == intern); // false
System.out.println(str == str1); // false
intern 方法调用时,常量池中已经存在 “计算机软件”(第一行代码),因此直接返回的常量池的地址,而 str1 指向的是堆里的地址。
换个顺序:
String str1 = new StringBuilder("计算机").append("软件").toString();
String intern = str1.intern();
String str = "计算机软件";
System.out.println(str == intern); // true
System.out.println(str1 == intern); // true
System.out.println(str == str1); // true
intern 方法调用时,常量池中并不存在 “计算机软件”,因此会将“计算机软件” 对应的引用地址(str1 地址)加入常量池,实际上是通过是以 hash 方式存储:hash(计算机软件)= str1 指向地址。
执行到 str = "计算机软件" 时, 会到常量池的 hash 表里查询,发现已经存在,就直接返回了 str1 地址,因此这三个都是指向堆内存里的相同一块内存。
值得注意的是,如果上面的代码放在 jdk 1.6 执行时,返回的则是 true/false/false,原因就是 jdk1.6 在执行 intern 方法时,会将该字符串实例复制到常量池中(位于永久代),因此返回的也就是字符串常量的地址,和 str1 的堆内存地址不同!
本文结束,有不明白地欢迎讨论交流~
如果觉得还不错的话,关注、分享、在看(关注不失联~), 原创不易,且看且珍惜~