和其餘多數程序設計語言同樣,Java 語言容許使用 +
鏈接兩個字符串。html
String name = "stephen"; String foo = "Hey, " + name;
當咱們將一個字符串和一個非字符串的值進行拼接時,並不會報錯:java
String name = "Stephen"; int age = 25; String foo = name + age; // 結果爲 Stephen25
其緣由是當 +
運算符左右兩邊有一個值是字符串時,會將另外一個值嘗試轉化爲字符串。oracle
咱們在瞭解字符串鏈接運算符前,先了解一下字符串轉換機制(String Conversion)。app
Any type may be converted to type
String
by string conversion.
若是值 x 是基本數據類型 T,那麼在字符串轉換前,首先會將其轉換成一個引用值,舉幾個例子:ide
• 若是 T 是 boolean 類型的,那麼就會用 new Boolean(x)
封裝一下;工具
• 若是 T 是 char 類型的,那麼就會用 new Character(x)
封裝一下;性能
• 若是 T 是 byte、short、int 類型的,那麼就會用 new Integer(x)
封裝一下;ui
咱們知道,對於基本數據類型,Java 都對應有一個包裝類(好比 int 類型對應有 Integer 對象),這樣操做之後,每一個基礎數據類型的值 x 都變成了一個對象的引用。設計
爲何這麼作?爲了統一對待,當咱們把基礎數據類型轉換成對應的包裝類的一個實例後,全部的值都是統一的對象引用。code
此時纔開始真正進行字符串轉換。咱們須要考慮兩種狀況:空值和非空值。
若是此時的值 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)
方法的代碼是幾乎同樣的。