配合反编译代码验证字符串初始化操作.相信看到这里, 再见到有关的面试题, 你已经无所畏惧了, 因为你已经懂得了背后原理 。
在结束之前我们不妨再做一道压轴题
文章插图
这道压轴题是经过精心设计的, 它不但照应上面所讲的字符串常量池知识, 也引出了后面的话题.如果看这篇文章是你第一次往底层探索字符串的经历, 那我估计你不能立即给出答案. 因为我第一次见这几行代码时也卡壳了 。
首先第一行和第二行是常规的字符串对象声明, 我们已经很熟悉了, 它们分别会在堆内存创建字符串对象, 并会在字符串常量池中进行注册 。
影响我们做出判断的是第三行代码 Strings3=s1+s2;, 我们不知道 s1+s2在创建完新字符串"hello world"后是否会在字符串常量池进行注册 。
说白了就是我们不知道这行代码是以双引号""形式声明字符串, 还是用new关键字创建字符串 。
这时, 我们应该去读一读这段代码的反编译代码. 如果你没有读过反编译代码, 不妨借此机会入门 。
在命令行中输入 javap-c对应.class文件的绝对路径, 按回车后即可看到反编译文件的代码段 。
文章插图
- 首先调用构造器完成Main类的初始化
- 0:ldc#2 // String hello
- 从常量池中获取"hello "字符串并推送至栈顶, 此时拿到了"hello "的引用
- 2:astore_1
- 将栈顶的字符串引用存入第二个本地变量s1, 也就是s1已经指向了"hello "
- 3:ldc#3 // String world
- 5:astore_2
- 重复开始的步骤, 此时变量s2指向"word"
- 6:new#4 // class java/lang/StringBuilder
- 刺激的东西来了: 这时创建了一个StringBuilder, 并把其引用值压到栈顶
- 9:dup
- 复制栈顶的值, 并继续压入栈定, 也就意味着栈从上到下有两份StringBuilder的引用, 将来要操作两次StringBuilder.
- 10:invokespecial#5 // Method java/lang/StringBuilder."<init>":()V
- 调用StringBuilder的一些初始化方法, 静态方法或父类方法, 完成初始化.
- 13: aload_1
- 把第二个本地变量也就是s1压入栈顶, 现在栈顶从上往下数两个数据依次是:s1变量和StringBuilder的引用
- 14:invokevirtual#6 // Method java/lang/StringBuilder.Append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
- 调用StringBuilder的append方法, 栈顶的两个数据在这里调用方法时就用上了.
- 接下来又调用了一次append方法(之前StringBuilder的引用拷贝两份就用途在此)
- 完成后, StringBuilder中已经拼接好了"hello world", 看到这里相信大家已经明白虚拟机是如何拼接字符串的了. 接下来就是关键环节
- 21:invokevirtual#7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
- 24:astore_3
- 拼接完字符串后, 虚拟机调用StringBuilder的 toString()方法获得字符串 hello world, 并存放至s3.
- 激动人心的时刻来了, 我们之所以不知道这道题的答案是因为不知道字符串拼接后是以new的形式还是以双引号""的形式创建字符串对象.
- 下面是我们追踪StringBuilder的 toString()方法源码:
文章插图
ok, 这道题解了, s3是通过new关键字获得字符串对象的 。
回到题目, 也就是说字符串常量表中没有存储"hello world"的引用, 当s4以引号的形式声明字符串时, 由于在字符串常量池中查不到相应的引用, 所以会在堆内存中新创建一个字符串对象. 所以s3和s4指向的不是同一个字符串对象, 结果为false 。
详解字符串操作类明白了字符串常量池, 我相信关于字符串的创建你已经有十足的把握了. 但是这还不够, 作为一名合格的Java工程师, 我们还必须对字符串的操作做到了如指掌. 注意! 不是说你不用查api能熟练操作字符串就了如指掌了, 而是说对String, StringBuilder, StringBuffer三大字符串操作类背后的实现了然于胸, 这样才能在开发的过程中做出正确, 高效的选择 。
String, StringBuilder, StringBuffer的底层实现
点进String的源码, 我们可以看见String类是通过char类型数组实现的 。
文章插图
接着查看StringBuilder和StringBuffer的源码, 我们发现这两者都继承自AbstractStringBuilder类, 通过查看该类的源码, 得知StringBuilder和StringBuffer两个类也是通过char类型数组实现的 。
推荐阅读
- 梦到从陡峭的山上下来 梦见自己站在陡峭的山上下不来
- 从ORM框架,聊一聊数据库的设计
- 三分钟见证:从沙子到芯片的过程
- 梦见自己从高处跳下来是什么意思 梦见自己从高处跳下来但是没事
- 聊聊离职补偿金
- 家具摆放
- 梦见去世的父亲来家里了 说想给我俩东西 梦见去世的父亲来家里了以前从没来过我家
- 学会这个方法,苹果手机续航能力明显提升
- 矿物油与合成机油差别在哪,你还在被4S店忽悠?看完从此远离小白
- 从HDR10到Dolby Vision电视,你所需要了解有关HDR电视信息都在这