Java 應用程式中的按值傳遞語義 (轉)

worldblog發表於2007-12-09
Java 應用程式中的按值傳遞語義 (轉)[@more@]

節選理解引數是按值而不是按引用傳遞的說明 應用有且僅有的一種引數傳遞機制,即按值傳遞。寫它是為了揭穿普遍存在的一種神話,即認為 Java 應用程式按引用傳遞引數,以避免因依賴“按引用傳遞”這一行為而導致的常見錯誤。

ack collected by developerWorks to formulate the content for this column, expanding on earlier explanations and clarifying areas that might have been blurred. Some of the feedback was positive, thanking me for clarifying this issue. Other feedback was not so positive, suggesting that I was confusing the issue or was altogether wrong about it.--&gt許多不同意我的讀者用 C++ 語言作為例子。因此,在此欄目中我將使用 C++ 和 Java 應用程式進一步闡明一些事實。是按引用傳遞的。物件確實是按引用傳遞的;節選與這沒有衝突。節選中說所有引數都是按值 -- 另一個引數 -- 傳遞的。下面的說法是正確的:在 Java 應用程式中永遠不會傳遞物件,而只傳遞物件引用。因此是按引用傳遞物件。但重要的是要區分引數是如何傳遞的,這才是該節選的意圖。Java 應用程式按引用傳遞物件這一事實並不意味著 Java 應用程式按引用傳遞引數。引數可以是物件引用,而 Java 應用程式是按值傳遞物件引用的。時,函式接收的是原始值的一個副本。因此,如果函式修改了該引數,僅改變副本,而原始值保持不變。按引用傳遞意味著當將一個引數傳遞給一個函式時,函式接收的是原始值的地址,而不是值的副本。因此,如果函式修改了該引數,程式碼中的原始值也隨之改變。

關於 Java 應用程式中引數傳遞的某些混淆源於這樣一個事實:許多程式設計師都是從 C++ 程式設計轉向 Java 程式設計的。C++ 既包含非引用型別,又包含引用型別,並分別按值和按引用傳遞它們。Java 程式語言有基本型別和物件引用;因此,認為 Java 應用程式像 C++ 那樣對基本型別使用按值傳遞,而對引用使用按引用傳遞是符合邏輯的。畢竟您會這麼想,如果正在傳遞一個引用,則它一定是按引用傳遞的。很容易就會相信這一點,實際上有一段時間我也相信是這樣,但這不正確。

在 C++ 和 Java 應用程式中,當傳遞給函式的引數不是引用時,傳遞的都是該值的一個副本(按值傳遞)。區別在於引用。在 C++ 中當傳遞給函式的引數是引用時,您傳遞的就是這個引用,或者記憶體地址(按引用傳遞)。在 Java 應用程式中,當物件引用是傳遞給方法的一個引數時,您傳遞的是該引用的一個副本(按值傳遞),而不是引用本身。請注意,呼叫方法的物件引用和副本都指向同一個物件。這是一個重要區別。Java 應用程式在傳遞不同型別的引數時,其作法與 C++ 並無不同。Java 應用程式按值傳遞所有引數,這樣就製作所有引數的副本,而不管它們的型別。

返回到 main 時,再次列印出這三個引數的值以及指標所指向的值。作為第一個和第二個引數傳遞的變數不受 modify 函式的影響,因為它們是按值傳遞的。但指標所指向的值改變了。請注意,與前兩個引數不同,作為最後一個引數傳遞的變數被 modify 函式改變了,因為它是按引用傳遞的。

現在考慮用 Java 語言編寫的類似程式碼:

清單 3:Java 應用程式

class Test { public static void main(String args[]) { int val; StringBuffer sb1, sb2; val = 10; sb1 = new StringBuffer("apples"); sb2 = new StringBuffer("pears"); System.out.println("val is " + val); System.out.println("sb1 is " + sb1); System.out.println("sb2 is " + sb2); System.out.println(""); System.out.println("calling modify"); //按值傳遞所有引數 modify(val, sb1, sb2); System.out.println("returned from modify"); System.out.println(""); System.out.println("val is " + val); System.out.println("sb1 is " + sb1); System.out.println("sb2 is " + sb2); } public static void modify(int a, StringBuffer r1, StringBuffer r2) { System.out.println("in modify..."); a = 0; r1 = null; //1 r2.append(" taste good"); System.out.println("a is " + a); System.out.println("r1 is " + r1); System.out.println("r2 is " + r2); } }


這段程式碼的輸出為:

清單 4:Java 應用程式的輸出

val is 10 sb1 is apples sb2 is pears calling modify in modify... a is 0 r1 is null r2 is pears taste good returned from modify val is 10 sb1 is apples sb2 is pears taste good


這段程式碼宣告瞭三個變數:一個整型變數和兩個物件引用。設定了每個變數的初始值並將它們列印出來。然後將所有三個變數作為引數傳遞給 modify 方法。

modify 方法更改了所有三個引數的值:

  • 將第一個引數(整數)設定為 0。
  • 將第一個物件引用 r1 設定為 null。
  • 保留第二個引用 r2 的值,但透過呼叫 append 方法更改它所引用的物件(這與前面的 C++ 示例中對指標 p 的處理類似)。

當執行返回到 main 時,再次列印出這三個引數的值。正如預期的那樣,整型的 val 沒有改變。物件引用 sb1 也沒有改變。如果 sb1 是按引用傳遞的,正如許多人聲稱的那樣,它將為 null。但是,因為 Java 程式語言按值傳遞所有引數,所以是將 sb1 的引用的一個副本傳遞給了 modify 方法。當 modify 方法在 //1 位置將 r1 設定為 null 時,它只是對 sb1 的引用的一個副本進行了該操作,而不是像 C++ 中那樣對原始值進行操作。

另外請注意,第二個物件引用 sb2 列印出的是在 modify 方法中設定的新字串。即使 modify 中的變數 r2 只是引用 sb2 的一個副本,但它們指向同一個物件。因此,對複製的引用所呼叫的方法更改的是同一個物件。

方法
假定我們知道引數是如何傳遞的,在 C++ 中編寫一個交換函式可以用不同的方式完成。使用指標的交換函式類似以下程式碼,其中指標是按值傳遞的:

清單 5:使用指標的交換函式

#include #include void s(int *a, int *b); int main (int argc, char** argv) { int val1, val2; val1 = 10; val2 = 50; swap(&val1, &val2); return 0; } void swap(int *a, int *b) { int temp = *b; *b = *a; *a = temp; }


使用引用的交換函式類似以下程式碼,其中引用是按引用傳遞的:

清單 6:使用引用的交換函式

#include #include void swap(int &a, int &b); int main (int argc, char** argv) { int val1, val2; val1 = 10; val2 = 50; swap(val1, val2); return 0; } void swap(int &a, int &b) { int temp = b; b = a; a = temp; }


兩個 C++ 程式碼示例都像所希望的那樣交換了值。如果 Java 應用程式使用“按引用傳遞”,則下面的交換方法應像 C++ 示例一樣正常工作:

清單 7:Java 交換函式是否像 C++ 中那樣按引用傳遞引數

class Swap { public static void main(String args[]) { Integer a, b; a = new Integer(10); b = new Integer(50); System.out.println("before swap..."); System.out.println("a is " + a); System.out.println("b is " + b); swap(a, b); System.out.println("after swap..."); System.out.println("a is " + a); System.out.println("b is " + b); } public static void swap(Integer a, Integer b) { Integer temp = a; a = b; b = temp; } }


因為 Java 應用程式按值傳遞所有引數,所以這段程式碼不會正常工作,其生成的輸入如下所示:

清單 8:清單 7 的輸出

before swap... a is 10 b is 50 after swap... a is 10 b is 50


那麼,在 Java 應用程式中如何編寫一個方法來交換兩個基本型別的值或兩個物件引用的值呢?因為 Java 應用程式按值傳遞所有的引數,所以您不能這樣做。要交換值,您必須用在方法呼叫外部用內聯來完成。

-- 按值傳遞 -- 這有助於使事情保持簡單。”

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990366/,如需轉載,請註明出處,否則將追究法律責任。

相關文章