java.lang.String 的 + 号操作到底做了什么?

2020-05-22 16:03:56来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

java.lang.String 的 + 号操作到底做了什么?

作者:丶Pz
https://www.cnblogs.com/panzi/p/11956782.html

在之前的面试经历中,对于String的考察还是挺频繁的,大致考察以下几个知识点:

  • String 常量池

  • new String()

  • == 和 equals 的区别

  • native 方法 String.intern()

虽然面试中大体答对了,但是今天早上微信群里的一个问题我却答不上来,这个问题是这样的:

String?str3?=?"what";
String?str4?=?str3?+?"?a?nice?day";
//运行时,?+?相当于 new,所以堆中会有?"what a nice day"对象,常量池中会有"what"," a nice day"两个对象,而不会有?"what a nice day"对象。
//这句话大佬们看看对不对啊,我怎么感觉不对啊
//常量池不会有"what a nice day"?对象吗?

看完这个问题,说实话我也是有点懵的,我只是知道 "what a nice day"不会在常量池,但是不知道具体的原因,后来群里的同学说 + 号是调用了?StringBuffer?的append 方法。

我去证实了,发现确实调用了 append 方法,但是当时没有 调用toString()方法,我很疑惑。(最后经过证实,是StringBuilder的append 方法,不是StringBuffer)。

代码验证

public?static?void?main(String[]?args)?{
????//#1
????String?str1?=?"what";
????//#2
????String?str2?=?str1?+?"?a?nice?day";
????//#3
????System.out.println("what?a?nice?day".equals(str2));
????//#4
????System.out.println("what?a?nice?day"?==?str2);
}

现在有以下几个问题,小伙伴们看看是否能答出来,即使答出来了,你知道为什么吗?

  1. str1 存放位置?

  2. str2 存放位置?

  3. 结果是 true 还是 false?

  4. 结果是 true 还是 false?

  5. "what a nice day" 存放在哪个位置呢?

解答分析(基于JDK1.8)

下面也不靠猜,我们直接查看生成的字节码:

localhost:test?didi$?javap?-verbose?-p?Main.class
Classfile?/develop/project/string-test/out/production/classes/com/fanpan26/string/test/Main.class
??Last?modified?2019-11-29;?size?972?bytes
??MD5?checksum?1d1f1a23bfe85c2f88d2f767e8aac314
??Compiled?from?"Main.java"
public?class?com.fanpan26.string.test.Main
??minor?version:?0
??major?version:?52
??flags:?ACC_PUBLIC,?ACC_SUPER
Constant?pool:
???#1?=?Methodref??????????#13.#34????????//?java/lang/Object."<init>":()V
???#2?=?String?????????????#35????????????//?what
???#3?=?Class??????????????#36????????????//?java/lang/StringBuilder
???#4?=?Methodref??????????#3.#34?????????//?java/lang/StringBuilder."<init>":()V
???#5?=?Methodref??????????#3.#37?????????//?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
???#6?=?String?????????????#38????????????//??a?nice?day
???#7?=?Methodref??????????#3.#39?????????//?java/lang/StringBuilder.toString:()Ljava/lang/String;
???#8?=?Fieldref???????????#40.#41????????//?java/lang/System.out:Ljava/io/PrintStream;
???#9?=?String?????????????#42????????????//?what?a?nice?day
??#10?=?Methodref??????????#43.#44????????//?java/lang/String.equals:(Ljava/lang/Object;)Z
??#11?=?Methodref??????????#45.#46????????//?java/io/PrintStream.println:(Z)V
??#12?=?Class??????????????#47????????????//?com/fanpan26/string/test/Main
??#13?=?Class??????????????#48????????????//?java/lang/Object
??#14?=?Utf8???????????????<init>
??#15?=?Utf8???????????????()V
??#16?=?Utf8???????????????Code
??#17?=?Utf8???????????????LineNumberTable
??#18?=?Utf8???????????????LocalVariableTable
??#19?=?Utf8???????????????this
??#20?=?Utf8???????????????Lcom/fanpan26/string/test/Main;
??#21?=?Utf8???????????????main
??#22?=?Utf8???????????????([Ljava/lang/String;)V
??#23?=?Utf8???????????????args
??#24?=?Utf8???????????????[Ljava/lang/String;
??#25?=?Utf8???????????????str1
??#26?=?Utf8???????????????Ljava/lang/String;
??#27?=?Utf8???????????????str2
??#28?=?Utf8???????????????StackMapTable
??#29?=?Class??????????????#24????????????//?"[Ljava/lang/String;"
??#30?=?Class??????????????#49????????????//?java/lang/String
??#31?=?Class??????????????#50????????????//?java/io/PrintStream
??#32?=?Utf8???????????????SourceFile
??#33?=?Utf8???????????????Main.java
??#34?=?NameAndType????????#14:#15????????//?"<init>":()V
??#35?=?Utf8???????????????what
??#36?=?Utf8???????????????java/lang/StringBuilder
??#37?=?NameAndType????????#51:#52????????//?append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
??#38?=?Utf8????????????????a?nice?day
??#39?=?NameAndType????????#53:#54????????//?toString:()Ljava/lang/String;
??#40?=?Class??????????????#55????????????//?java/lang/System
??#41?=?NameAndType????????#56:#57????????//?out:Ljava/io/PrintStream;
??#42?=?Utf8???????????????what?a?nice?day
??#43?=?Class??????????????#49????????????//?java/lang/String
??#44?=?NameAndType????????#58:#59????????//?equals:(Ljava/lang/Object;)Z
??#45?=?Class??????????????#50????????????//?java/io/PrintStream
??#46?=?NameAndType????????#60:#61????????//?println:(Z)V
??#47?=?Utf8???????????????com/fanpan26/string/test/Main
??#48?=?Utf8???????????????java/lang/Object
??#49?=?Utf8???????????????java/lang/String
??#50?=?Utf8???????????????java/io/PrintStream
??#51?=?Utf8???????????????append
??#52?=?Utf8???????????????(Ljava/lang/String;)Ljava/lang/StringBuilder;
??#53?=?Utf8???????????????toString
??#54?=?Utf8???????????????()Ljava/lang/String;
??#55?=?Utf8???????????????java/lang/System
??#56?=?Utf8???????????????out
??#57?=?Utf8???????????????Ljava/io/PrintStream;
??#58?=?Utf8???????????????equals
??#59?=?Utf8???????????????(Ljava/lang/Object;)Z
??#60?=?Utf8???????????????println
??#61?=?Utf8???????????????(Z)V
{
??public?com.fanpan26.string.test.Main();
????descriptor:?()V
????flags:?ACC_PUBLIC
????Code:
??????stack=1,?locals=1,?args_size=1
?????????0:?aload_0
?????????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V
?????????4:?return
??????LineNumberTable:
????????line?6:?0
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature
????????????0???????5?????0??this???Lcom/fanpan26/string/test/Main;

??public?static?void?main(java.lang.String[]);
????descriptor:?([Ljava/lang/String;)V
????flags:?ACC_PUBLIC,?ACC_STATIC
????Code:
??????stack=3,?locals=3,?args_size=1
?????????0:?ldc???????????#2??????????????????//?String?what
?????????2:?astore_1
?????????3:?new???????????#3??????????????????//?class?java/lang/StringBuilder
?????????6:?dup
?????????7:?invokespecial?#4??????????????????//?Method?java/lang/StringBuilder."<init>":()V
????????10:?aload_1
????????11:?invokevirtual?#5??????????????????//?Method?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
????????14:?ldc???????????#6??????????????????//?String??a?nice?day
????????16:?invokevirtual?#5??????????????????//?Method?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
????????19:?invokevirtual?#7??????????????????//?Method?java/lang/StringBuilder.toString:()Ljava/lang/String;
????????22:?astore_2
????????23:?getstatic?????#8??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
????????26:?ldc???????????#9??????????????????//?String?what?a?nice?day
????????28:?aload_2
????????29:?invokevirtual?#10?????????????????//?Method?java/lang/String.equals:(Ljava/lang/Object;)Z
????????32:?invokevirtual?#11?????????????????//?Method?java/io/PrintStream.println:(Z)V
????????35:?getstatic?????#8??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
????????38:?ldc???????????#9??????????????????//?String?what?a?nice?day
????????40:?aload_2
????????41:?if_acmpne?????48
????????44:?iconst_1
????????45:?goto??????????49
????????48:?iconst_0
????????49:?invokevirtual?#11?????????????????//?Method?java/io/PrintStream.println:(Z)V
????????52:?return
??????LineNumberTable:
????????line?9:?0
????????line?11:?3
????????line?13:?23
????????line?15:?35
????????line?16:?52
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature
????????????0??????53?????0??args???[Ljava/lang/String;
????????????3??????50?????1??str1???Ljava/lang/String;
???????????23??????30?????2??str2???Ljava/lang/String;
??????StackMapTable:?number_of_entries?=?2
????????frame_type?=?255?/*?full_frame?*/
??????????offset_delta?=?48
??????????locals?=?[?class?"[Ljava/lang/String;",?class?java/lang/String,?class?java/lang/String?]
??????????stack?=?[?class?java/io/PrintStream?]
????????frame_type?=?255?/*?full_frame?*/
??????????offset_delta?=?0
??????????locals?=?[?class?"[Ljava/lang/String;",?class?java/lang/String,?class?java/lang/String?]
??????????stack?=?[?class?java/io/PrintStream,?int?]
}
SourceFile:?"Main.java"

从Constant pool: 中的信息可以看到,#2 、#6、#9 可以解答上文中的1,5两个问题。

  • str1 是存放在常量池的

  • "what a nice day" (非str2)也是存放在常量池的

下面我们看一下 + 操作做了什么事情,可以在Code中看到,该操作调用了 StringBuilder.append 方法

11:?invokevirtual?#5??????????????????//?Method?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14:?ldc???????????#6??????????????????//?String??a?nice?day
16:?invokevirtual?#5??????????????????//?Method?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19:?invokevirtual?#7??????????????????//?Method?java/lang/StringBuilder.toString:()Ljava/lang/String;

那么到这里一切都答案都出来了

  • str2 是存放在堆中

  • equals 为 true

  • == 为 false

所以说其实 str1 + " a nice day" 就相当于 new StringBuilder().append(str1).append(" a nice day");

//这两种写法生成的字节码是一样的。
//String?str2?=?str1?+?"?a?nice?day";
String?str2?=?new?StringBuilder().append(str1).append("?a?nice?day").toString();

StringBuffer的toString 方法如下:

@Override
public?String?toString()?{
????//?所以说 str2 其实是一个 new String,是不在常量池里面的。
????return?new?String(value,?0,?count);
}

总结

通过类的字节码(3种骚操作,教你查看 Java 字节码)可以查看底层具体用什么方式实现,所以说虽然看似一个简单的String问题,其实往深处挖掘还是考察了对生成的字节码的理解。

还有,遇到一个问题,不能死记答案,有些人告诉你,+ 操作就是 new 对象,但是具体到底是不是或者为什么是有没有思考过呢?上文中如有错误,欢迎指出。

试一试

/**
?*?以下程序输出的结果是什么?
?*?*/
public?static?void?main(String[]?args)?{
????String?str1?=?"what";
????String?str2?=?str1?+?"?a?nice?day";
????System.out.println("what?a?nice?day".equals(str2));
????System.out.println("what?a?nice?day"?==?str2);
}

/**
?*?以下程序输出的结果是什么?
?*?*/
public?static?void?main(String[]?args)?{
????String?str1?=?"what?a?nice?day";
????String?str2?=?new?String("what?a?nice?day");
????System.out.println(str1.equals(str2));
????System.out.println(str1?==?str2);
}


/**
?*?以下程序输出的结果是什么?
?*?*/
public?static?void?main(String[]?args)?{
????String?str1?=?"what";
????String?str2?=?str1.concat("?a?nice?day");
????System.out.println("what?a?nice?day".equals(str2));
????System.out.println("what?a?nice?day"?==?str2);
????System.out.println("what?a?nice?day"==str2.intern());
}

推荐去我的博客阅读更多:

1.Java JVM、集合、多线程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、后端、架构、阿里巴巴等大厂最新面试题

觉得不错,别忘了点赞+转发哦!


原文链接:https://www.cnblogs.com/javastack/p/12935062.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:重磅!!Redis 6.0.0 已发布,有史以来改变最大的版本

下一篇:LeetCode 105. 从前序与中序遍历序列构造二叉树