java.lang.String 的 + 號操作到底做了什麼事情?


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"
/<init>/<init>/<init>/<init>/<init>/<init>

從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架構等各個熱門領域。


分享到:


相關文章: