不同的程式語言,會有一些相類似的語法或元素,例如程式語言都會有if、for、while之類語法,也大多有字元、數值、字串之類的元素,然而各種程式語言解決的問題不同,也因此在這些類似語法或元素中,各程式語言會有細微、重要且不容忽視的特性,在學習程式語言時,不得不慎。
以Java的字串來說,就有一些必須注意的特性:
- 字串常量與字串池
- 不可變動(Immutable)字串
字串常量與字串池
來看個程式片段,你覺得以下會顯示true或false?
char[] name = {'J', 'u', 's', 't', 'i', 'n'};
String name1 = new String(name);
String name2 = new String(name);
System.out.println(name1 == name2);
希望現在的你有足夠的能力與自信回答出false的答案,因為name1與name2分別參考至建構出來的String物件,那麼底下這個程式碼呢?
String name1 = "Justin";
String name2 = "Justin";
System.out.println(name1 == name2);
很意外地,答案會是true!這代表了name1與name2是參考到同一物件囉?答案是對的!在Java中為了效率考量,以""包括的字串,只要內容相同(序列、大小寫相同),無論在程式碼中出現幾次,JVM都只會建立一個String實例,並在字串池(String pool)中維護。在上面這個程式片段的第一行,JVM會建立一個String實例放在字串池中,並給name1參考,而第二行則是讓name2直接參考至字串池中的String實例,如下圖所示:
用""寫下的字串稱為字串常量(String literal),既然你用
"Justin"寫死了字串內容,基於節省記憶體考量,自然就不用為這些字串常量分別建立String實例。來看個實務上不會如此撰寫,但認證上很常考的問題:String name1 = "Justin";
String name2 = "Justin";
String name3 = new String("Justin");
String name4 = new String("Justin");
System.out.println(name1 == name2);
System.out.println(name1 == name3);
System.out.println(name3 == name4);
這個片段會分別顯示
true、false、false的結果,因為"Justin"會建立String實例並在字串池中維護,所以name1與name2參考的是同一個物件,而new一定是建立新物件,所以name3與name4分別參考至新建的String實例。以圖來表示的話就可以知道為何會顯示true、false、false的結果:先前一直強調,如果你想比較物件實質內容是否相同,不要使用
==,要使用equals()。同樣地,如果想比較字串實際字元內容是否相同,不要使用==,要使用equals()。以下程式片段執行結果都是顯示true:String name1 = "Justin";String name2 = "Justin";String name3 = new String("Justin");String name4 = new String("Justin");System.out.println(name1.equals(name2));System.out.println(name1.equals(name3));System.out.println(name3.equals(name4));不可變動字串
在Java中,字串物件一旦建立,就無法更動物件中任何內容,物件上沒有任何方法可以更動字串內容。那麼使用
+串接字串是怎麼達到的?例如:String name1 = "Java";String name2 = name1 + "World";System.out.println(name2);上面這個程式片段會顯示JavaWorld,由於無法更動字串物件內容,所以絕不是在
name1參考的字串物件之後附加World內容。可以試著反組譯這段程式,結果會發現:String s = "Java";String s1 = (new StringBuilder()).append(s).append("World").toString();System.out.println(s1);如果使用
+串接字串,會變成建立java.lang.StringBuilder物件,使用其append()方法來進行+左右兩邊字串附加,最後再轉換為toString()傳回。簡單來說,使用
+串接字串會產生新的String實例,這並不是告訴你,不要使用+串接字串,畢竟+串接字串很方便,這只是在告訴你,不要將+用在重複性的串接場合,像是迴圈中或遞迴時使用+串接字串,這會因為頻繁產生新物件,造成效能上的負擔。舉個例子來說,如果使用程式顯示下圖的結果,你會怎麼寫呢?
這是個很有趣的題目,以下列出幾個我看過的寫法。首先有人這麼寫:
for(int i = 1; i < 101; i++) { System.out.print(i); if(i != 100) { System.out.print('+'); }}這可以達到題目要求,不過有沒有效能上可以改進的空間?其實可以改成這樣:
for(int i = 1; i < 100; i++) { System.out.printf("%d+", i);}System.out.println(100);程式變簡潔了,而且可以少一個
if判斷,不過就這個小程式而言,少個if判斷是節省不了多少時間,事實上,你可以減少輸出次數,因為在for迴圈中呼叫了99次System.out.printf(),相較於記憶體中的運算,標準輸出速度是慢得多了,有的人知道可以使用+串接字串,所以會這麼寫:String text = "";for(int i = 1; i < 100; i++) { text = text + i + '+';}System.out.println(text + 100);這個程式片段中,在
for迴圈中沒有進行輸出,這確實改善了不少效能,不過在Java中使用+串接會產生新字串物件,這個程式片段for迴圈中有頻繁產生新物件的問題,正如先前對+串接片段反組譯中所看到的,你可以改用StringBuilder來改善:package cc.openhome;
public class OneTo100 {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
for (int i = 1; i < 100; i++) {
builder.append(i).append('+');
}
System.out.println(builder.append(100).toString());
}
}
StringBulder每次append()呼叫過後,都會傳回原有的StringBuilder物件,方便你進行下一次的操作。這個程式片段只產生了一個StringBuilder物件,只進行一次輸出,效能上對比最初看到的程式片段好得多。java.lang.StringBulder是JDK5之後新增的類別,在這版本之前,是使用java.lang.StringBuffer類別,StringBuilder與StringBuffer具有相同操作介面,在單機非多執行緒(Multithread)情況下,使用StringBuilder會有較好的效率,因為StringBuilder不處理同步(Synchronized)問題;StringBuffer則會處理同步問題,在多執行緒環境下建議改用StringBuffer,讓物件自行管理同步問題。之後還會介紹何為多執行緒。再來看個很無聊但認證會考的題目,請問以下會顯示
true或false? String text1 = "Ja" + "va";String text2 = "Java";System.out.println(text1 == text2);有的人會這麼說:因為用了
+串接字串,所以產生新字串,所以text1 == text2應該是false吧!如果你這麼認為,那就上當了!答案是true!反組譯之後就知道為什麼了:String s = "Java";String s1 = "Java";System.out.println(s == s1);編譯器是這麼認為的:既然你寫死了
"Ja" + "va",那你要的不就是"Java"嗎?根據以上反組譯之後的程式碼,顯示true的結果就不足為奇了。

