在Java中,要建立字串很方便:
String str = "Java";
依Java命名慣例而言,String這個名稱首字大寫,無疑地應該是個類別,這代表了,str是參考至一個String的實例。有些書會說,這個寫法其實等同於以下的寫法,而且這樣有建立物件的明確語義:
String str = new String("Java");
這並不是全部的事實。這句話比較像是:
String str1 = "Java";
String str2 = new String(str1);
String str2 = new String(str1);
也就是先前的那行程式碼,其實JVM建立了兩個String實例。這意謂著,直接使用雙引號包括字元來建立字串,以及自行用new關鍵字建立字字串是不同的,這可以由以下的程式碼來驗證:
String str1 = "Java";
String str2 = new String(str1);
System.out.println(str1 == str2);
String str2 = new String(str1);
System.out.println(str1 == str2);
==運算子會比較兩個參考名稱是否參考至同一物件,上面的程式片段會印出false,也就是str1與str2是參考至不同物件。直接使用雙引號包括字元來建立字串,JVM會自行在記憶體中使用一個字串池(String pool)來維護,只要雙引號含括的字元內容相同(序列相同,大小寫相同),無論在程式碼中出現幾次,在字串池中都只有一個實例。
下面這段程式碼可以驗證:
String str1 = "Java";
String str2 = "Java";
System.out.println(str1 == str2);
String str2 = "Java";
System.out.println(str1 == str2);
這個程式碼片段會印出true,因為雙引號含括的內容都是Java這個字元序列,雖然程式碼中出現了兩次"Java",但在字串池中卻只有一個實例,只不過依程式碼所定義的,被str1與str2所參考著。
一般書籍都會說,要比較字串是否相等要使用equals()方法而不是==,這個意思是指比較字串所含字元序列的相等性,而非參考名稱所參考的記憶體位置相等性。下面這個程式碼會顯示true,因為str1與str2所參考之物件,其所含字元序列都相等:
String str1 = "Java";
String str2 = new String(str1);
System.out.println(str1.equals(str2));
String str2 = new String(str1);
System.out.println(str1.equals(str2));
每個字串都是不可變動的,這表示你一旦建立字串,就不可以修改它的字元內容。字串的字元內容,是維護在String中的字元陣列中:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
下面這段程式碼,只不過是將字串內含字元陣列給另一個字串在內部複製或參考:
public String(String original) {
int size = original.count;
char[] originalValue = original.value;
char[] v;
if (originalValue.length > size) {
v = Arrays.copyOfRange(originalValue, off, off+size);
} else {
v = originalValue;
}
this.offset = 0;
this.count = size;
this.value = v;
}
其它的字串建構式也是類似的。String上還有個intern()方法,可以讓你將字串放入字串池,或者是從字串池中取得JVM所維護的字串。如果你呼叫它,則會使用equals()方法,比較字串池中是否有字元序列相同的字串,如果有則傳回,如果無則將該字串置入字串池。
所以下面這個程式碼執行結果會是true:
String str1 = "Java";
String str2 = new String(str1);
System.out.println(str1 == str2.intern());
String str2 = new String(str1);
System.out.println(str1 == str2.intern());
在Java中,可以使用+串接字串,例如:
String str1 = "Java";
String str2 = "Cool";
String str3 = str1 + str2;
System.out.println(str3);
String str2 = "Cool";
String str3 = str1 + str2;
System.out.println(str3);
最後顯示的是JavaCool。一應該都會告訴你,用+串接字串很方便,但要小心+會產生新的字串。這也不是全部的事實,因為它產生的更多,如果使用的是JDK5以上,可以實際反組譯看看:
String s = "Java";
String s1 = "Cool";
String s2 = (new StringBuilder()).append(s).append(s1).toString();
System.out.println(s2);
String s1 = "Cool";
String s2 = (new StringBuilder()).append(s).append(s1).toString();
System.out.println(s2);
如 果是JDK1.4以下,則會在JVM內部產生StringBuffer完成類似的字串附加動作。StringBuilder或StringBuffer, 內部也是使用自動增加的字元陣列來維護,若長度不夠,則會產生新的更長的字元陣列,然後作字元陣列複製的動作。所以若是有頻繁串接字串的動作,例如在迴圈 中串接SQL之類的,會有效能上的隱憂,應當避免。
不過下面這個稍微有點不同:
String str = "Java" + "Cool";
System.out.println(str);
System.out.println(str);
執行結果一樣顯示JavaCool,不過反組譯它,你會發現編譯器很聰明:
String s = "JavaCool";
System.out.println(s);
System.out.println(s);
既然兩個都是雙引號括著,又直接使用+串接,那你要的不就是"JavaCool"?
附帶一提的是,字串與物件之間也是可以使用+串接的!例如:
Map map = new HashMap();
map.put("key", "value");
System.out.println("Map: " + map);
map.put("key", "value");
System.out.println("Map: " + map);
這沒什麼!如果字串與物件使用+串接,其實最後會呼叫物件的toString()取得物件的字串描述,也就是說類似於:
Map map = new HashMap();
map.put("key", "value");
System.out.println("Map: " + map.toString());
map.put("key", "value");
System.out.println("Map: " + map.toString());
這也不完全是事實,上面只是比喻!編譯器(或JVM)作的更多:
HashMap hashmap = new HashMap();
hashmap.put("key", "value");
System.out.println(
(new StringBuilder()).append("Map: ").append(hashmap).toString());
hashmap.put("key", "value");
System.out.println(
(new StringBuilder()).append("Map: ").append(hashmap).toString());
至於StringBuilder的append()作了什麼,留待你自行去探索一下它的原始碼吧!
延伸閱讀 String and memory leaks。