转 原文链接:https://blog.csdn.net/qq_44919287/article/details/113754892
下列代码输出的结果是什么?
String str = new StringBuilder("ss").append("ss").toString();
System.out.println(str == str.intern());
String str1 = new StringBuilder("ja").append("va").toString();
System.out.println(str1 == str1.intern());
结果是:一个true加一个false
问题很简单,解释如下:
“ss"和"ss"拼接成"ssss”,变量str指向堆中的"ssss",常量池中是没有"ssss"的。
intern()方法在常量池中的进行如下操作,如果有"ssss"就返回已有"ssss"的地址值,如果没有就在常量池中创建一个"ssss"并返回。这个时候就有人要问了一个指向堆空间,一个指向常量池,不应该返回false吗。这样想是对的,不过是在jdk7之前是对的,jdk7开始intern()方法逻辑变了。如果常量池中没有"ssss",不是立即创建,而是看看堆中有没有,如果有就直接用堆中的数据。所以两个引用指向的都是堆中的"ssss",返回true。
“ja"和"va"拼接成"java”,s1指向的是堆中的"java",和上面一样,本应该不在常量池中创建,直接使用堆中的"java",但"java"这个字符串很特殊。他是jvm自带的字符串。所以常量池中是有"java"字符串的,所以直接使用常量池中数据。两个引用指向不同返回false。
复现代码时发现的问题
为了不出现”脑子会了,手不会"的局面,我在idea上手写了一下,但写着写着发现情况不对。
String s = new StringBuilder("ss").append("ss").toString();
System.out.println(s == s.intern());
结果为true
String s1 = new StringBuilder("ssss").toString();
System.out.println(s1 == s1.intern());
结果为false
提示:这两串代码是在不同的类中写的,写在同一个类会有干扰
为什么会这样呢?上面比下面就多了一个append()方法结果就反了,append无非是拼接字符串,没什么特殊操作。况且如果看一下StringBuilder的构造方法,他的底层也是append()来实现的。
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
这是什么情况呢?要搞清楚这个问题,必须先搞清楚String的创建过程。正常情况下创建String的方式主要是一下两种方法:
String s = "sss";
Stirng s1 = new String("sss");
都是简单常用的方式,第一种直接在常量池中创建"sss"并返回,第二种是在堆中创建一个"sss",然后看看常量池中有没有"sss",没有也给创建一个,但是返回的是堆中的对象地址。
如果你了解上述过程(大部分人都了解),又不巧对StringBuilder的源码了解不多,那么只能恭喜你,StringBuilder会把你坑的很惨。
回到正题,既然结果不同是事实,最简单的方法就是逆向推导。jdk7之前intern()方法一大作用就是保证了引用一定是指向常量池的。但变动之后这一功能直接没了,所以==判断的是堆池,还是堆堆,无法确定。
那只能一步一步分析了,无论使没使用append()都不影响,因为new StringBuilder()底层调用的就是append(),所以问题只能在这几个常量上。new StringBuilser("…")和append("…")都会在常量池中创建对应的字符串,这个看字节码就能看出来。ldc拿取,一定是存在的字符串。
也就是说,现在常量池中一定有"ss"字符串,两个ss拼接变成ssss在堆中,然后调用toString()方法,toString()的底层是new String(),上面讲过new String()会创建一个或两个对象,所以。。。。。。一套逻辑下来发现不对啊,堆中常量池中因为new String的存在一定都有ssss啊,应该是false啊。
这里就要解释一下原始题目隐藏的知识点了,再放一遍原始题目:
String str = new StringBuilder("ss").append("ss").toString();
System.out.println(str == str.intern());
String str1 = new StringBuilder("ja").append("va").toString();
System.out.println(str1 == str1.intern());
为什么这里的常量池没有"ssss"对象呢?不是调用toString方法了吗,不就相当于调用了new String吗?应该有对象才对啊。对,应该有,但是他就是没有。答案只有一个,这个new String有鬼,看一下源码,调用的不是new String("…"),而是new String(char value[], int offset, int count)。也就是说这个三个参数的new String没有在常量池中创建对象,因为他底层只是复制了一下。详细的推理,我不会,源码我也看不懂,我只是个菜鸡。但我可以复现啊
String s = new String(new char[]{'a','b'},0,2);
翻看对应的字节码常量池,没有"ab"。
现在答案已经清晰明了了,后面的两个之所以一个为true一个为false。因为第一段代码的常量池中只有"ss",intern()也不能创建,new String(…,…,…)也不会创建,所以都调用的是堆中的;第二段代码因为new StringBuilder(“ssss”)直接在常量池中创建了一个"ssss"所以不必调用堆中对象,为false。
这是本人的第一篇博客,可能废话有点多,不会插入图片,更不会作图,如果觉得讲的不够直观,还请见谅。