我相信每个Java程序员对String都熟悉的不能再熟悉了,可以说只要敲代码,就绕不过String.
但是 越是我们熟悉的人,我们就越容易忽略掉他的一些细节.
下面是一个简单但不简约的String的面试题.
小伙伴们,自习思考一下,看看答案跟你们想的是不是很不一样呐~~
public class TestString1 {
public static void main(String[] args) {
String s1 = new String("1") + new String("2");
String s2 = "12";
System.out.println(s1 == s2);
System.out.println("-----------------------------------");
String s3 = new String("1") + new String("3");
s3.intern();
String s4 = "13";
System.out.println(s3 == s4);
System.out.println("-----------------------------------");
String s5 = new String("1") + new String("4");
String s6 = s5.intern();
System.out.println(s5 == s6);
System.out.println("-----------------------------------");
String s7 = new String("1") + new String ("8");
String s8 = "18";
s7.intern();
System.out.println(s7 == s8);
}
}
小伙伴们是不是都思考完了,下面我来揭晓答案.
当当当当,JDK1.6及以前版本的答案:
JDK1.7及以后版本的答案:
这个题看起来很简单,但是坑却很多.要讲明白这个题也需要首先明确几个知识点:
首先是第一个天坑,这个坑就是StringTable位置的变化.
这个故事要从JDK1.7开始说起.
在JDK1.7之前,StringTable 是放在永久代的.在JDK1.7的时候,Java对StringTable的结构做出了重大的调整,将StringTable放入到了堆之中.
这也是造成上面两个版本答案不一样的情况.
然后我们再来说一说这个题的另外一个坑,String.intern()方法.
这是JDK源码中对这个方法的描述.其中比较重要的两个信息是用红线标出来的地方.
当我们调用这个方法时,如果本来这个字符串在字符串常量池中存在(用equals比较为true)的话,我们就返回这个字符串.
如果本来这个字符串在字符串常量池中不存在的时候,我们就把这个字符串添加到常量池中,并返回这个字符串的引用.
if and only if (当且仅当) 两个字符串equal为真的时候, 他们的intern比较才会为真.
另外一个方面,这个面试题还考察字符串拼接的底层实现.
那么又引出了一个题:
public class TestString2 {
public static void main(String[] args) {
String str1 = "a";
String str2 = "b";
String str3 = "ab";
System.out.println(str3 == str1 + str2);
String str4 = "a" + "b";
System.out.println(str3 == str4);
String str5 = str1 + "b";
System.out.println(str3 == str5);
final String str6 = "a";
String str7 = str6 + "b";
System.out.println(str3 == str7);
}
}
这个题的答案小伙伴们知道么~
怎么样,答对了么~~关于字符串拼接,我总结出了一点: 只要拼接符号两侧有任何一个是变量, 那么这个拼接出来的字符串 就不 == 与本来就在字符串常量池中的字符串.
有变量参加的字符串拼接的底层是使用StringBuilder来实现的.
String str5 = str1 + "b";
//首先,要new StringBuilder(),这里我们假设这个变量是sb,那么就是
StringBuilder sb = new StringBuilder();
//然后,调用append方法
sb.append(str1);
sb.append("b");
//然后调用toString方法.
//而toSring()方法是返回一个 new String(value, 0, count);
str5 = sb.toString();
这也就是说有了变量参加的字符串拼接最后的返回 的是一个新new出来的String对象.一会我会画图说明一下
而且!!!.有一个非常重要的知识点是. 当我们执行完之后,其实在常量池中,并没有"ab" 这一个字符串.
这里要划重点哟~~
**
那么让我们回到这个面试题本身来.
**
String s1 = new String("1") + new String("2");
String s2 = "12";
System.out.println(s1 == s2);//java 1.6 和java 1.8 答案都是false
System.out.println("-----------------------------------");
就像之前说的,S1 相当于另外new了一个对象,而对象的保存的是"12" .
通过看字节码文件也能看出来
而,这个字节码文件也从一方面证明了,我们上面说的结论:在执行完拼接或者说是new String(“xxx”)时,字符串常量池中并没有生成对应的字符串.
所以,第一种情况 无论是1.6还是1.8都是false;
String s3 = new String("1") + new String("3");
s3.intern();
String s4 = "13";
System.out.println(s3 == s4);
1.6及以前的原理:
当S3.intren()时, 因为此时字符串常量池没有"13",所以,我们就会在常量池中添加这个字符串"13".而当S4=“13"的时候,就直接引用了常量池中的字符串,所以S3 == S4 = false;
1.7及以后:
而当字符串常量池放入到堆空间中之后,为了节约空间,当我们使用.intern()方法时,常量池中不再时创建一个"13”,而是直接使用了之前"13"的地址值. 所以此时S3==S4是true
第三种情况:
String s5 = new String("1") + new String("4");
String s6 = s5.intern();
System.out.println(s5 == s6);
这个基本原理就跟第二种一样了.
第三种情况:
String s7 = new String("1") + new String ("8");
String s8 = "18";
s7.intern();
System.out.println(s7 == s8);
因为执行顺序问题. S7 = new String(“18”) 之后没有立即执行.intern()方法,而S8这个时候直接 =“18”, 这样就使得 常量池中就创建了常量"18".所以,无论是1.6还是1.8当中,都是false.
以上~