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

码影弦歌者
• 阅读 1777
作者:丶Pz
https://www.cnblogs.com/panzi...

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

在之前的面试经历中,对于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());
}
大家可以关注下栈长的微信公众号:Java技术栈,回复:福利,可以免费获取一份我整理的 2020 最新 Java 面试题,真的非常全(含答案),无任何套路。

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

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

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

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

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Stella981 Stella981
4年前
React Hooks实现异步请求实例—useReducer、useContext和useEffect代替Redux方案
<blockquote本文是学习了2018年新鲜出炉的ReactHooks提案之后,针对<strong异步请求数据</strong写的一个案例。注意,本文假设了:<br1.你已经初步了解<codehooks</code的含义了,如果不了解还请移步<ahref"https://reactjs.org/docs/hooksintro.html
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究