zoukankan      html  css  js  c++  java
  • 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();
    

    而StringBuilder 的toString 方法如下:

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

    总结

    通过类的字节码可以查看底层具体用什么方式实现,所以说虽然看似一个简单的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实现 LeetCode 697 数组的度(类似于数组的map)
    Java实现 LeetCode 697 数组的度(类似于数组的map)
    Java实现 LeetCode 697 数组的度(类似于数组的map)
    Java实现 LeetCode 696 计数二进制子串(暴力)
    Java实现 LeetCode 696 计数二进制子串(暴力)
    Java实现 LeetCode 696 计数二进制子串(暴力)
    Java实现 LeetCode 695 岛屿的最大面积(DFS)
    Java实现 LeetCode 695 岛屿的最大面积(DFS)
    PHP serialize() 函数
    PHP print_r() 函数
  • 原文地址:https://www.cnblogs.com/panzi/p/11956782.html
Copyright © 2011-2022 走看看