对于这种题,你总能很快的给出标准答案:==比较的是对象地址,equals方法比较的是真正的字符数组。所以输出的是false和true。
上面的属于最低阶的题目,没有什么难度。
现在这种老套的题目已经慢慢消失了,取而代之的是有一些变形的新题目:
String s1 = "aa";
String s2 = "bb";
String str1 = s1 + s2;
String str2 = "aabb";
//输出什么呢???
System.out.println(str1 == str2);
final String s3 = "cc";
final String s4 = "dd";
String str3 = s3 + s4;
String str4 = "ccdd";
//又输出什么呢???
System.out.println(str3 == str4);
难度提升了一些,但思考一下也不难得出答案是false和true。
今天的文章就是以这几个题目展开的。
String对象的创建
先简单看一下String类的结构:
可以发现,String里面有一个value属性,是真正存储字符的char数组。
在执行String s = "xyz";
的时候,在堆区创建了一个String对象,一个char数组对象。
如何证明创建了一个String对象和一个char数组对象呢?我们可以通过IDEA的Debug功能验证:
注意看我截图的位置,在执行完String s = "xyz";
之后,再次点击load classes,Diff栏的String和char[]分别加了1,表示在内存中新增了一个char数组对象和一个String对象。
现在,我们再来看String s = new String("xyz");
创建了几个对象。
从这张Debug动图中,我们可以得出在String s = new String("xyz");
之后,创建了两个String对象和一个char数组对象。
又因为String s = new String("xyz");
的s
引用只能指向一个对象,可以画出内存分布图:
从图中可以看到,在堆区,有两个String对象,这两个String对象的value都指向同一个char数组对象。
那么问题来了,下面的那个String对象根本就没被引用,也就是说他没有被用到,那么它到底是干什么的呢?
占了内存空间又不使用,难道这是JDK的设计缺陷?
很显然不是JDK的缺陷,JDK虽然确实有设计缺陷,但不至于这么明显,这么愚蠢。
那下面的那个String对象是干什么的呢?
答案是用于驻留到字符串常量池中去的,注意,这里我用了一个驻留
,并不是直接把对象放到字符串常量池里面去,有什么区别我们后面再讲。
你只需要知道,字符串常量池在JVM源码中对应的类是StringTable,底层实现是一个Hashtable。
我们以String s = new String("xyz");
为例:
首先去找字符串常量池找,看能不能找到“xyz”字符串对应对象的引用,如果字符串常量池中找不到:
- 创建一个String对象和char数组对象
- 将创建的String对象封装成HashtableEntry,作为StringTable的value进行存储
- new String(“xyz”)会在堆区又创建一个String对象,char数组直接指向创建好的char数组对象
如果字符串常量池中能找到:
- new String(“xyz”)会在堆区创建一个对象,char数组直接指向已经存在的char数组对象
而String s = "xyz";
是怎么样的逻辑:
首先去找字符串常量池找,看能不能找到“xyz”字符串的引用,如果字符串常量池中能找不到:
- 创建一个String对象和char数组对象
- 将创建的String对象封装成HashtableEntry,作为StringTable的value进行存储
- 返回创建的String对象
如果字符串常量池中能找到:
- 直接返回找到引用对应的String对象
总结而言就是:
对于String s = new String("xyz");
这种形式创建字符串对象,如果字符串常量池中能找到,创建一个String对象;如果如果字符串常量池中找不到,创建两个String对象。
对于String s = "xyz";
这种形式创建字符串对象,如果字符串常量池中能找到,不会创建String对象;如果如果字符串常量池中找不到,创建一个String对象。
所以,在日常开发中,能用String s = "xyz";
尽量不用String s = new String("xyz");
,因为可以少创建一个对象,节省一部分空间。
需要强调的是,字符串常量池存的不是字符串也不是String对象,而是一个个HashtableEntry,HashtableEntry里面的value指向的才是String对象,为了不让表述变得复杂,我省略了HashtableEntry的存在,但不代表它就不存在。
上文提到的驻留就是新建HashtableEntry指向String对象,并把HashtableEntry存入字符串常量池的过程。
在网上一些文章中,一些作者可能是为了让读者更好的理解,省略了一些这些,一定要注意辨别区分。
达成以上共识之后,我们再回顾一下那个老套的笔试题。
String s1 = new String("xyz");
String s2 = "xyz";
//为什么输出的是false呢?
System.out.println(s1 == s2);
//为什么输出的是true呢?
System.out.println(s1.equals(s2));
有了上面的基础之后,我们画出对应的内存图,s1 == s2为什么是false就一目了然了。
因为equals方法比较的真正的char数据,而s1和s2最终指向的都是同一个char数组对象,所以s1.equals(s2)等于true。
关于他们最终指向的都是同一个char数组对象这一观点,也可以通过反射证明:
我修改了str1指向的String对象的value,str2指向的对象也被影响了。
字符串拼接
现在,我们再来看一下变式题:
String s1 = "aa";
String s2 = "bb";
String str1 = s1 + s2;
String str2 = "aabb";
//为什么输出的是false
System.out.println(str1 == str2);
对于这个题目,我们需要先看一下这段代码的字节码。
字节码指令看不懂没有关系,看我用红色框框起来的部分就行了,可以看到居然出现了StringBuilder。
最后
本人也收藏了一份Java面试核心知识点来应付面试,借着这次机会可以送给我的读者朋友们
目录:
Java面试核心知识点
CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】
一共有30个专题,足够读者朋友们应付面试啦,也节省朋友们去到处搜刮资料自己整理的时间!
Java面试核心知识点
已经有读者朋友靠着这一份Java面试知识点指导拿到不错的offer了
一共有30个专题,足够读者朋友们应付面试啦,也节省朋友们去到处搜刮资料自己整理的时间!
[外链图片转存中…(img-iAyZSjVX-1631096060499)]
Java面试核心知识点
已经有读者朋友靠着这一份Java面试知识点指导拿到不错的offer了
[外链图片转存中…(img-t5bnKbfG-1631096060500)]