面试题:String a = "ab"; String b = "a" + "b"; a == b 是否相等
面试考察点
考察目的:考察对JVM基础知识的理解,涉及到常量池、JVM运行时数据区等。
考察范围:工作2到5年。
背景知识
要回答这个问题,需要搞明白两个最基本的问题
String a=“ab”
,在JVM中发生了什么?String b=“a”+“b”
,底层是如何实现?
JVM的运行时数据
首先,我们一起来复习一下JVM的运行时数据区。
为了让大家有一个全局的视角,我从类加载,到JVM运行时数据区的整体结构画出来,如下图所示。
对于每一个区域的作用,在我之前的面试系列文章中有详细说明,这里就不做复述了。
当使用String a=“Hello”
这种方式创建字符串对象时,JVM首先会先检查该字符串对象是否存在与字符串常量池中,如果存在,则直接返回常量池中该字符串的引用。否则,会在常量池中创建一个新的字符串,并返回常量池中该字符串的引用。(这种方式可以减少同一个字符串被重复创建,节约内存,这也是享元模式的体现)。
如下图所示,如果再通过
String c=“Hello”
创建一个字符串,发现常量池已经存在了Hello
这个字符串,则直接把该字符串的引用返回即可。(String里面的享元模式设计)
总结:intern方法会从字符串常量池中查询当前字符串是否存在:
若不存在就会将当前字符串放入常量池中,并返回当地字符串地址引用。
如果存在就返回字符串常量池那个字符串地址。
注意,所有字符串字面量在初始化时,会默认调用
intern()
方法。这段程序,之所以
a==b
,是因为声明a
时,会通过intern()
方法去字符串常量池中查找是否存在字符串Hello
,由于不存在,则会创建一个。同理,变量b
也同样如此,所以b
在声明时,发现字符常量池中已经存在Hello
的字符串常量,所以直接返回该字符串常量的引用。public static void main(String[] args) { String a="Hello"; String b="Hello"; }
OK,学习到这里,是不是感觉自己懂了?我出一道题目来考考大家,下面这段程序的运行结果是什么?
public static void main(String[] args) {
String a =new String(new char[]{'a','b','c'});
String b = a.intern();
System.out.println(a == b);
String x =new String("def");
String y = x.intern();
System.out.println(x == y);
}
正确答案是:
true
false
第二个输出为false
还可以理解,因为new String(“def”)
会做两件事:
在字符串常量池中创建一个字符串
def
。new
关键字创建一个实例对象string
,并指向字符串常量池def
的引用。
而x.intern()
,是从字符串常量池获取def
的引用,他们的指向地址不同,我后面的内容还会详细解释。
第一个输出结果为true
是为啥捏?
JDK文档中关于
intern()
方法的说明:当调用intern
方法时,如果常量池(内置在 JVM 中的)中已经包含相同的字符串,则返回池中的字符串。否则,将此String
对象添加到池中,并返回对该String
对象的引用。
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4);
}
答案是false
。
因为上述程序等价于, s3
和s4
指向不同的地址引用,自然不相等。
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
StringBuilder sb=new StringBuilder().append(s1).append(s2);
String s4 = sb.toString();
System.out.println(s3 == s4);
}
总结:只有足够清晰的理解了字符串常量池相关的所有知识点,不管面试过程中如何变化,你都能准确回答,这就是知识的力量!