JAVA值傳參和引用傳參

青色刀客發表於2013-08-01
這是個老生常談的問題了,經過了先輩們無數的爭論,但是還是沒有提出一個令所有人滿意的答案。當然,我在這裡只是就我自己對Java和其他語言的一些認識談談自己的理解和看法。 
首先,我們要明確下面這兩條不可爭論的事實了: 
【1】、物件是傳引用的 
【2】、基本型別是傳值的
 
我對這個問題是這樣理解的: 
有過C++背景的朋友可能都會有一個認識,因為C/C++傳參有很多種選擇,直接傳遞物件或值,或者傳遞指標,傳遞引用等。而大家都有個共識,那就是在傳遞基本資料型別時,選擇第一種,而傳遞物件的時候則選用傳指標或者引用。這點可以從資料傳輸效率的角度上得到解釋。 
而Java創始人James在某些方面還是得借鑑一些C的思想的,所以上述兩條就比較好理解了。 
個人對傳參的理解是: 
函式引數相對其他欄位,在編譯器裡肯定會有特殊處理的,而一般的編譯器則是這樣處理: 
在執行棧中拷貝一份傳遞過來的引數(值參),而後面函式體的關於值參的操作實際上都是針對執行棧裡儲存的備份操作的。可以用這種理解驗證一下C++的三種情況。例如傳遞過來的是引用,則編譯器的處理流程是這樣的: 
首先拷貝傳遞來的值參(注意:值參也是引用,Java裡new之後獲得的都是物件的引用,而不是物件本身,這點和c++很不同),然後將其壓入執行棧,後面的操作也就是對這份拷貝(也是對預期的物件的引用,所以可以到達物件)操作了。 

我們看看以下的幾個例子來增加對上述思想的理解。 

Example1: 
Java程式碼  收藏程式碼
  1. package com.shansun.ref;  
  2.   
  3. public class RefInteger {  
  4.   
  5.     public RefInteger() {  
  6.     }  
  7.   
  8.     public void swap(Integer para1, Integer para2) {  
  9.         Integer tmp;  
  10.         tmp = para1;  
  11.         para1 = para2;  
  12.         para2 = tmp;  
  13.     }  
  14.   
  15.     public void print(int int1, int int2) {  
  16.         System.out.println("Int1 is " + int1);  
  17.         System.out.println("Int2 is " + int2);  
  18.     }  
  19.   
  20.     public static void main(String[] args) {  
  21.         Integer int1 = new Integer(10);  
  22.         Integer int2 = new Integer(50);  
  23.   
  24.         RefInteger ref = new RefInteger();  
  25.         System.out.println("---------SWAP UNABLE----------");  
  26.         ref.swap(int1, int2);  
  27.         ref.print(int1, int2);  
  28.     }  
  29. }  

執行結果: 
---------SWAP UNABLE---------- 
Int1 is 10 
Int2 is 50 

我們都知道一點:void swap(int, int)是不能達到交換兩個引數的值的。我們這裡試試使用Integer物件呢?同樣也交換失敗,原因很簡單:void swap(Integer para1, Integer para2)函式傳遞進來的是兩個型別為Integer類的引用,而在函式體裡實現的功能只是改變了para1和para2引用的物件,而para1和para2在記憶體中的值卻一點變化都沒有。可能這樣理解更加形象一點: 
例如,A省省長是小王,B省省長是小張,這樣牽強的理解為小王是A省的一個引用,小張則是B省的一個引用。在執行swap(小王,小張)後,產生的效果只是:現在A省省長是小張,B省省長是小王了,即現在小張代表的不再是B省,而是A省了,而對於物件本身,如A省,其地理位置,人口數量等都沒有變化。不能說在swap語句執行後,A省跑到地圖上B省的位置去了吧? 

Example2: 
Java程式碼  收藏程式碼
  1. package com.shansun.ref;  
  2.   
  3. public class RefObject {  
  4.   
  5.     int val;  
  6.   
  7.     public RefObject(int val) {  
  8.         this.val = val;  
  9.     }  
  10.   
  11.     public void setVal(int val) {  
  12.         this.val = val;  
  13.     }  
  14.   
  15.     public int getVal() {  
  16.         return this.val;  
  17.     }  
  18.   
  19.     public void print() {  
  20.         System.out.println("Now val is " + val);  
  21.     }  
  22.   
  23.     public static void swap(RefObject obj1, RefObject obj2) {  
  24.         RefObject tmp;  
  25.         tmp = obj1;  
  26.         obj1 = obj2;  
  27.         obj2 = tmp;  
  28.     }  
  29.   
  30.     public static void swapx(RefObject obj1, RefObject obj2) {  
  31.         int tmp;  
  32.         tmp = obj1.getVal();  
  33.         obj1.setVal(obj2.getVal());  
  34.         obj2.setVal(tmp);  
  35.     }  
  36.   
  37.     public static void main(String[] args) {  
  38.         RefObject obj1 = new RefObject(10);  
  39.         RefObject obj2 = new RefObject(50);  
  40.         obj1.print();  
  41.         obj2.print();  
  42.         swap(obj1, obj2);  
  43.         obj1.print();  
  44.         obj2.print();  
  45.         swapx(obj1, obj2);  
  46.         obj1.print();  
  47.         obj2.print();  
  48.     }  
  49. }  

執行結果: 
---------SWAP UNABLE---------- 
Int1 is 10 
Int2 is 50 
---------BEFORE SWAP---------- 
Int1 is 20 
Int2 is 30 
---------SWAP ENABLE---------- 
Int1 is 30 
Int2 is 20 

首先和上面例子一樣,swap(obj1, obj2)也是換湯不換藥的,所以交換失敗。但是swapx則交換資料成功了,我們還拿上面省長的例子來解釋這個現象: 
國家要求小王代表的A省的C市市長和小張代表的B省的D市市長交換職位,可以嗎?當然可以了,所以交換成功。 

我們最後再回到Example1的問題,如果我就想交換兩個int值呢?其實也不是沒有辦法的。 
Java程式碼  收藏程式碼
  1. package com.shansun.ref;  
  2.   
  3. public class RefInteger {  
  4.   
  5.     int int1, int2;  
  6.   
  7.     public RefInteger(int int1, int int2) {  
  8.         this.int1 = int1;  
  9.         this.int2 = int2;  
  10.     }  
  11.   
  12.     public void swap() {  
  13.         int tmp;  
  14.         tmp = int1;  
  15.         int1 = int2;  
  16.         int2 = tmp;  
  17.     }  
  18.   
  19.     public void print() {  
  20.         System.out.println("Int1 is " + int1);  
  21.         System.out.println("Int2 is " + int2);  
  22.     }  
  23.   
  24.     public static void main(String[] args) {  
  25.         RefInteger ref = new RefInteger(2030);  
  26.         System.out.println("---------BEFORE SWAP----------");  
  27.         ref.print();  
  28.         System.out.println("---------SWAP ENABLE----------");  
  29.         ref.swap();  
  30.         ref.print();  
  31.     }  
  32. }  


執行結果: 
---------BEFORE SWAP---------- 
Int1 is 20 
Int2 is 30 
---------SWAP ENABLE---------- 
Int1 is 30 
Int2 is 20 

小記: 
C++:使用分界符&標示引用 
C#: 使用ref關鍵字標示引用 

好了,關於傳參的問題我的理解就是這樣的。如有錯誤,敬請指出。 

相關文章