咱也來做面試題(一)
身邊有些做Java開發的朋友,找工作時常常被考到一道關於字串的題目。題目倒是很基礎,然而根據朋友們事後的描述,有理由認為有的面試官自己都沒有完全搞清楚這個問題。此外,在CSDN論壇中我也多次看到一些朋友在這個問題上的迷惑。索性把自己的理解寫下來吧。
題目是一道簡單的小程式,像下面這樣:
public class Test1 { public static void main(String args[]) { String s = new String("Hello"); System.out.println(s); foo(s); System.out.println(s); } public static void foo(String s) { s = new String("World"); } }
問程式先後兩次分別會輸出什麼。
第一個肯定輸出“Hello”。關鍵是第二個,個別基礎不牢的朋友可能被考倒,但基礎稍微紮實一點的就不會。第二個輸出的也是“Hello”。
到這裡,萬事大吉。面試官兩眼放出喜悅的光芒,讚歎道:“嗯,不錯不錯。不會變的,對吧。因為String是immutable型別,immutable型別改不了的。”他說的“immutable”是指String類沒有任何一個方法會改變物件的狀態。
這可就有問題了。確實程式兩次輸出的都是“Hello”,但原因卻不是String的immutable特性。不信換一個“mutable”的試試,比如StringBuilder或StringBuffer。
public class Test2 {
public static void main(String args[]) {
StringBuilder s = new StringBuilder("Hello");
System.out.println(s);
foo(s);
System.out.println(s);
}
public static void foo(StringBuilder s) {
s = new StringBuilder("World");
}
}
這次呢?會先輸出“Hello”,然後輸出“World”嗎?不會,仍然是兩次“Hello”。
足以說明這個問題跟String的immutable特性沒有一毛錢的關係。不管是immutable型別,還是一般的型別,用這種方式寫出來的程式main方法中前後兩個物件肯定是一樣的。
真正的原因是:Java語言的引數傳遞機制是“按值傳遞”(pass by value)。雖然Java中除基本數值型別外,其它變數都是引用,但那是另一回事。變數的語義(“引用”還是“值”)跟函式傳參的機制是兩個正交的概念。
各種程式語言中,最常見的引數傳遞方式不外乎按值傳遞和按引用傳遞(pass by reference)兩種。比如,C和Java中函式引數都是按值傳遞的,C++和C#則同時支援按值傳遞和按引用傳遞。C和Java的不同在於,C是值型別的按值傳遞,而Java是引用型別的按值傳遞(基本的數值型別除外)。
按值傳參最大的特點就是函式內部對“形參變數”本身的所做的修改外面的“實參變數”感知不到。
不過,對於StringBuilder來說我們至少有辦法讓它改變,比如像下面這樣:
public class Test3 {
public static void main(String args[]) {
StringBuilder s = new StringBuilder("Hello");
System.out.println(s);
foo(s);
System.out.println(s);
}
public static void foo(StringBuilder s) {
s.replace(0, s.length(), "World");
}
}
體會到不同了嗎?雖然引數變數本身是按值傳遞的,但這次我們對變數本身不感興趣,我們不改變變數本身,而是通過它直接修改它所引用的那個物件。這一次,Java語言“幾乎一切皆引用”的特點起作用了,程式第一次輸出“Hello”,第二次輸出“World”。熟悉C的朋友可能馬上聯想到:這很像C語言中通過指標來修改它指向的內容。
而先前那個String版本的程式,我們無法寫出一個相應的可變版本。為什麼呢?……“嗯,不錯不錯。因為String是immutable型別……”,這次真對了。——可見先前說“沒有一毛錢的關係”也不盡然。因為immutable,導致我們無法針對String寫出一個像Test3那樣的“可變”程式,如此說來,“二分錢的關係”應該是有的。
“pass reference by value”就像C++ 11中的“An lvalue with rvalue reference type”一樣,乍一看挺繞的,但只要仔細想清楚,讓正交的東西“塵歸塵 土歸土”,就可以加深對語言的理解。
順便問一句,您知道java.lang.StringBuilder和java.lang.StringBuffer的區別嗎?好多面試官都喜歡“順便”問問這個,而且答對了不會加分,答不上來卻會扣分,至少扣印象分。:(