1. 程式人生 > >Java方法引數是引用呼叫還是值呼叫?——值呼叫

Java方法引數是引用呼叫還是值呼叫?——值呼叫

   方法呼叫(call by) 是一個標準的計算機科學術語。方法呼叫根據引數傳遞的情況又分為值呼叫( call by reference ) 引用呼叫( call by value ) 。江湖上有很多關於這兩種呼叫的定義 ,最通常的說法是傳遞值的是值呼叫,傳遞地址的是引用呼叫。這其實很不恰當,這種 這些說法很容易讓我們聯想到Java的物件引數傳遞是引用呼叫,實際上,Java的物件引數傳遞仍然是值呼叫 。 

      我們首先用一段程式碼來證實一下為什麼Java的物件引數傳遞 是值呼叫。

Java程式碼  收藏程式碼
  1. public class Employee {  
  2.     public
     String name=null;  
  3.     public Employee(String n){  
  4.         this.name=n;  
  5.     }  
  6.     //將兩個Employee物件交換  
  7.     public static void swap(Employee e1,Employee e2){  
  8.         Employee temp=e1;  
  9.         e1=e2;  
  10.         e2=temp;  
  11.                 System.out.println(e1.name+" "+e2.name); //列印結果:李四 張三  
  12.     }  
  13.     //主函式  
  14.     public static void main(String[] args) {  
  15.         Employee worker=new Employee("張三");  
  16.         Employee manager=new Employee("李四");  
  17.         swap(worker,manager);  
  18.         System.out.println(worker.name+" "+manager.name); //列印結果仍然是: 張三 李四  
  19.     }  
  20. }  

      上面的結果讓人很失望,雖然形參物件e1,e2的內容交換了,但實參物件worker,manager並沒有互換內容。這裡面最重要的原因就在於形參e1,e2是實參worker,manager的地址拷貝。

      大家都知道,在Java中物件變數名實際上代表的是物件在堆中的地址(專業術語叫做物件引用 )。在Java方法呼叫的時候,引數傳遞的是物件的引用。重要的是,形參和實參所佔的記憶體地址並不一樣,形參中的內容只是實參中儲存的物件引用的一份拷貝。

       如果大家對JVM記憶體管理中Java棧 區域性變數區 有所瞭解的話(可以參見《 Java 虛擬機器體系結構 》),就很好理解上面這句話。在JVM執行上面的程式時,執行main方法和swap方法,會在Java棧中先後push兩個叫做棧幀的記憶體空間。main棧幀中有一塊叫區域性變數區的記憶體用來儲存實參物件worker和manager的引用。而swap棧幀中的區域性變數區則儲存了形參物件e1和e2的引用。雖然e1和e2的引用值分別與worker和manager相同,但是它們佔用了不同的記憶體空間。當e1和e2的引用發生交換時,下面的圖很清晰的看出完全不會影響worker和manager的引用值。

             

      Java物件引數傳遞雖然傳遞的是地址(引用),但仍然是值呼叫。是時候需要給引用呼叫和值呼叫一個準確的定義了。

     值呼叫(call by value)  在引數傳遞過程中,形參和實參佔用了兩個完全不同的記憶體空間。形參所儲存的內容是實參儲存內容的一份拷貝。實際上,Java物件的傳遞就符合這個定義,只不過形參和實參所儲存的內容並不是常規意義上的變數值,而是變數的地址。咳,回過頭想想:變數的地址不也是一種值嗎!

      引用呼叫(call by reference)  在引數傳遞的過程中,形參和實參完全是同一塊記憶體空間,兩者不分彼此。實際上,形參名和實參名只是程式設計中的不同符號,在程式執行過程中,記憶體中儲存的空間才是最重要的。不同的變數名並不能說明佔用的記憶體儲存空間不同。

      大體上說,兩種呼叫的根本並不在於傳遞的是值還是地址(畢竟地址也是一個值),而是在於形參和實參是否佔用同一塊記憶體空間。事實上,C/C++的指標引數傳遞也是值呼叫,不信試試下面的C程式碼吧!

C程式碼  收藏程式碼
  1. #include<stdio.h>  
  2. void swap(int *a1,int *b1){  
  3.     int *t=a1;  
  4.     a1=b1;  
  5.     b1=t;  
  6. }  
  7. int main(){  
  8.     int x1=100;  
  9.     int x2=200;  
  10.         int *a=&x1;  
  11.     int *b=&x2;  
  12.     printf("%d %d\n",*a,*b);  
  13.     swap(a,b);  
  14.     printf("%d %d\n",*a,*b);  
  15.     return 0;  
  16. }  

         但C/C++是有引用呼叫的,這就是C/C++一種叫做引用的變數宣告方法: int a; int &ra=a; 其中ra是a的別名,兩者在記憶體中沒有區別,佔用了同一個記憶體空間。而通過引用(別名)的引數傳遞就符合引用呼叫的特點了。大家可以去試試

void swap(int &a1,int &b1);的執行結果。

   我們首先用一段程式碼來證實一下為什麼Java的物件引數傳遞 是地址傳遞。

public class Employee {

	public String name=null;
	
	public Employee(String n){
		this.name=n;
	}
	
	public static void change(Employee e1){
		e1.name = "李四";
        System.out.println(e1.name); //列印結果:李四
	}
	//主函式
	public static void main(String[] args) {
		Employee worker=new Employee("張三");
		change(worker);
		System.out.println(worker.name); //列印結果仍然是: 李四
	}
}