雲栖号資訊:【 點選檢視更多行業資訊】
在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!
和其他多數程式設計語言一樣,Java 語言允許使用 + 連接配接兩個字元串。
String name = "stephen";
String foo = "Hey, " + name;
當我們将一個字元串和一個非字元串的值進行拼接時,并不會報錯:
String name = "Stephen";
int age = 25;
String foo = name + age; // 結果為 Stephen25
其原因是當 + 運算符左右兩邊有一個值是字元串時,會将另一個值嘗試轉化為字元串。
字元串轉換機制
我們在了解字元串連接配接運算符前,先了解一下字元串轉換機制(String Conversion)。
Any type may be converted to type String by string conversion.
如果值 x 是基本資料類型 T,那麼在字元串轉換前,首先會将其轉換成一個引用值,舉幾個例子:
- 如果 T 是 boolean 類型的,那麼就會用 new Boolean(x) 封裝一下;
- 如果 T 是 char 類型的,那麼就會用 new Character(x) 封裝一下;
- 如果 T 是 byte、short、int 類型的,那麼就會用 new Integer(x) 封裝一下;
我們知道,對于基本資料類型,Java 都對應有一個包裝類(比如 int 類型對應有 Integer 對象),這樣操作以後,每個基礎資料類型的值 x 都變成了一個對象的引用。
為什麼這麼做?為了統一對待,當我們把基礎資料類型轉換成對應的包裝類的一個執行個體後,所有的值都是統一的對象引用。
此時才開始真正進行字元串轉換。我們需要考慮兩種情況:空值和非空值。
如果此時的值 x 是 null,那麼最終的字元串轉換結果就是一個字元串 null;
否則就會調用這個對象的 toString() 的無參方法。
前者很好了解,後者我們一起來看看:
在 Java 所有的父類 Object 中,有一個重要的方法就是 toString 方法,它傳回表示對象值的一個字元串。在 Object 類中對 toString 的定義如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
該方法傳回對象的類名和散列碼。如果類沒有重寫 toString 方法,預設就會調用它的父類的 toString 方法,而此時我們的值 x 統一都是對象值,是以一定有 toString 方法可以調用并列印出值(也有個特殊,如果調用 toString 傳回的值是一個 null 值,那麼就會用字元串 null 代替)。
字元串連接配接符
當 + 運算符左右兩邊參與運算的表達式的值有一個為字元串時,那麼在程式運作時會對另一個值進行字元串轉換。
這裡需要注意的是 + 運算符同時作為算術運算符,在含有多個值參與運算的時候,要留意優先級,比如下面這個例子:
String a = 1 + 2 + " equals 3";
String b = "12 eqauls " + 1 + 2;
變量 a 的結果是 3 equals 3,變量 b 的結果是 12 equals 12。
有些人這裡可能會有疑問,解釋一下,第一種情況根據運算優先級是先計算 1+2 那麼此時的 + 運算符是算術運算符,是以結果是 3,然後再和 " equals 3" 運算,又因為 3 + " equals 3" 有一個值為字元串,是以 + 運算符是字元串連接配接運算符。
在運作時,Java 編譯器一般會使用類似 StringBuffer/StringBuilder 這樣帶緩沖區的方式來減少通過執行表達式時建立的中間 String 對象的數量,進而提高程式性能。
我們可以用 Java 自帶的反彙編工具 javap 簡單的看一下:
假設有如下這段代碼:
public class Demo {
public static void main(String[] args) {
int i = 10;
String words = "stephen" + i;
}
}
然後編譯,再反彙編一下:
javac Demo.java
javap -c Demo
可以得到如下内容:
Compiled from "Demo.java"
public class Demo {
public Demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 10
2: istore_1
3: new #2 // class java/lang/StringBuilder
6: dup
7: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
10: ldc #4 // String stephen
12: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: iload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: return
}
我們可以發現,Java 編譯器在執行字元串連接配接運算符所在表達式的時候,會先建立一個 StringBuilder 對象,然後将運算符左邊的字元串 stephen 拼接(append)上去,接着在拼接右邊的整型 10,然後調用 StringBuilder 的 toString 方法傳回結果。
如果我們拼接的是一個對象呢?
public class Demo {
public static void main(String[] args) {
Demo obj = new Demo();
String words = obj + "stephen";
}
@Override
public String toString() {
return "App{}";
}
}
一樣的做法,我們會發現此時 Method java/lang/StringBuilder.append:(Ljava/lang/Object;) 也就是 StringBuilder 調用的是 append(Object obj) 這個方法,我們檢視 StringBuilder 類的 append 方法:
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
而 String.valueOf(obj) 的實作代碼如下:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
也就是會調用對象的 toString() 方法。
可能到這裡大家會有一個疑問:上面不是說字元串轉換對于基本類型是先轉換成對應的包裝類,然後調用它的 toString 方法嗎,這邊怎麼都是調用 StringBuilder 的 append 方法了呢?
實作方式不同,其實是本質上是一樣的,隻不過為了提高性能(減少建立中間字元串等的損耗),Java 編譯器采用 StringBuilder 來做。感興趣的可以自己去追蹤下 Integer 包裝類的 toString 方法,其實和 StringBuilder 的 append(int i) 方法的代碼是幾乎一樣的。
【雲栖号線上課堂】每天都有産品技術專家分享!
課程位址:
https://yqh.aliyun.com/live立即加入社群,與專家面對面,及時了解課程最新動态!
【雲栖号線上課堂 社群】
https://c.tb.cn/F3.Z8gvnK
原文釋出時間:2020-06-23
本文作者:是阿亮啊
本文來自:“
掘金”,了解相關資訊可以關注“掘金”