這是Java中一個很經典的問題,在stack-overflow上有很多類似的問題,然而很多回答都是錯的或者回答不完整;如果你不深入思考的話,會認為這個問題很簡單,但是當你深入下去,會發現這個問題很容易讓人產生困惑。
package simplejava; public class Q14 { public static void change(String x) { x = "cd"; } public static void main(String[] args) { String x = new String("ab"); change(x); System.out.println(x); } }
結果打印:
C++版本如下:
void change(string &x) { x = "cd"; }
int main(){ string x = "ab"; change(x); cout << x << endl; }
打印結果:
x變量存儲了堆中“ab”對象的引用,當x作為一個參數傳入到change()方法內部時,仍然指向堆中的“ab”對象,如下所示:
因為Java是按值傳遞的,x的值是“ab”對象的引用,當方法change()被調用時,創建了一個新的對象“cd”,然後x指向“cd”對象,如下圖所示:
這看起來像是一個完美的解釋,他們很清楚Java總是按值傳遞的。但是,問題到底出在哪裡呢?
上面的解釋有若干處錯誤,為了更加容易的理解該問題,我們還是先理清下整個過程。
當字符串對象“ab”被創建的時候,Java分配了對應大小的內存空間,然後對象被賦值給變量x,事實上是x變量存儲的是對象的引用,這個引用是“ab”對象在內存中的地址;
x變量包含了對象的引用,x並不是“ab”對象,而是一個存儲了“ab”對象引用(內存地址)的變量。
Java是按值傳遞的,當x被傳入change()方法的時候,事實上傳入的是一個x變量的拷貝。然後在方法change()內部創建了另一個對象“cd”,它有一個不同的引用。真正改變的是這個x變量的拷貝,其值變成了“cd”對象的引用,而不是原始的x變量被改變;
注:感覺說得有點混亂,我的理解,先說這個參數x,其相當於一個局部變量,當使用該參數的時候,將會分配一個新的存儲位置,將實參拷貝到該位置,並將該拷貝值傳遞給該方法;
在change()方法內部,執行x = "cd"的時候,這裡的x實際上是main方法的x變量的一個拷貝,一開始其存放的是“ab對象”的引用,執行完這段代碼後,其值變成“cd”對象的引用,而main方法的x變量並沒有改變,存放的仍然是“ab”對象的引用。
這個問題的原因跟字符串的不變形沒任何關系,即使將String替換成StringBuilder對象,結果仍然不變,關鍵點是變量存儲的是對象的引用,而不是對象本身;
如果真的想改變這個對象的值,
首先這個對象是要可改變的,例如StringBuilder。
其次,我們要保證沒有新對象被創建賦值給參數變量,因為Java只能按值傳遞。
如下代碼:
public static void main(String[] args) { StringBuilder x = new StringBuilder("ab"); change(x); System.out.println(x); }
public static void change(StringBuilder x) { x.delete(0, 2).append("cd"); }
譯文鏈接:http://www.programcreek.com/2013/09/string-is-passed-by-reference-in-java/