Fork me on GitHub

6/17/2011

[Java] 淺談 call by value 和 call by reference

在 C++ 中, call-by-value 意味著把變數的值複製一份傳進函數;  而 call-by-reference 則意味著把變數的 alias (用 &var 或者指標) 傳進函數當中。

因此,在 C++ 中我是這麼理解的,在函數內部改變 call-by-value 傳入的參數並不會影響外部變數,因為傳進來的是它的複製品;  而改變 call-by-reference 傳入的參數則會影響到外部變數,因為 alias 是指向同一個 value 的。

基於這樣的理解,在學習 Java 的時候遇到「只有 call-by-value」這樣的觀念,便在讓我產生了疑惑:「要怎麼讓傳入函數的變數隨著函數內部對它操作而跟著變動」,也就是說 Java 要怎麼做到 call-by-reference 的語意呢?

後來發現,原本我以為 Java 的 call-by-value 和 C++ 的 call-by-value 有相同的立足點 (將物件複製一份丟進函數) 實際上是錯誤的。

原來,在 Java 當中,除了原生型別的變數,一般型別的變數其實不是直接 hold 物件本身,而是 hold 物件的 reference!

這點跟 C++ 就有一點出入了,在 C++ 中要用一個變數 hold 一個物件的 reference,只得明確用指標,像是 Dog* aDog = new Dog;  這樣一來,操作上也得用 -> 運算子,像是 aDog->setName('Lucky');  如果是用 Dog aDog;aDog 這個變數就是指物件本身(傳進函數會被複製一份)。

Java 沒有指標的觀念 (全物件導向),除了原生型別,它的物件都需要用 new 建立 (跟 C++ 的 new 又不太一樣,C++ new 是用來動態配置記憶體用的)  如 Dog aDog = new Dog;  這裡的 aDog 並不是 Dog 型別的物件本身,而是物件的一個 reference

因此「要怎麼讓傳入函數的變數隨著函數內部對它操作而跟著變動」這個疑惑就解開了,Java 所謂的「只有 call-by-value」的 value 指的就是「物件的 reference」,既然物件的 reference 被複製一份進去函數,那當然可以透過函數內部操作該 reference 來改變外部的物件 (而且不需要用 -> 運算元)。

這裡又冒出一個問題,既然原生型別的物件在 Java 當中是直接 hold 物件的值,所以傳入函數參數的預設行為就是 call-by-value,那麼如果想要實現 call-by-reference 的語意該怎麼做呢?其實也很簡單,就是將該原生型別的變數包裝成一個陣列,再傳入函數即可。

public static void increment(int[] array, int amount)
{
   array[0] = array[0] + amount;
}

public static void main(String args[])
{
   int[] myInt = { 1 };

   increment (myInt, 5);

   System.out.println ("Array contents : " + myInt[0]);
}

參考資料:
Java is Pass-by-Value, Dammit! - Scott Stanchfield
http://javadude.com/articles/passbyvalue.htm

Q&A : How do I pass a primitive data type by reference?
http://www.javacoffeebreak.com/faq/faq0066.html

... ...

No comments:

Post a Comment