1. 程式人生 > >java引用傳遞和值傳遞的詳細探討

java引用傳遞和值傳遞的詳細探討

問題來源於一道廣泛的面試題:當一個物件被當作引數傳遞到一個方法後,此方法可改變這個物件的屬性,並可返回變化後的結果,那麼這裡到底是值傳遞還是引用傳遞?
為了解決這個問題,查閱了各種資料,卻發現沒有統一的結果,因此只能從我自己的角度給出一個答案。歡迎大家探討這個問題。

根據Horstmann的《java核心技術》(中文第8版P115-P117)的描述,java中是沒有引用傳遞的,原文摘錄如下:
”java程式設計語言總是採用值呼叫。也就是說,方法得到的是所有引數值的一個拷貝,特別是,方法不能修改傳遞給它的任何引數變數的內容。“

”有些程式設計師(甚至是本書的作者),認為java程式設計語言對物件採用的是引用呼叫,實際上這種理解是不對的。”

首先明確一下值傳遞和引用傳遞的概念:
值傳遞:表示方法接收的是呼叫者提供的值。
引用傳遞:表示方法接收的是呼叫者提供的變數地址。

下面這段程式碼中,顯而易見,percent的值是不會改變的。

    double percent=10;
    editPercent(percent);
    System.out.println("方法結束後的percent:"+percent);

    public static void editPercent(double x){
        x=3*x;
        System.out.println("方法內的X:"+x);
    }

而在以下這段程式碼中,傳遞了一個Emplyee物件,通過呼叫方法,改變了Employee物件的屬性。

    Employee boss= new Employee("boss", 5000);
    editSalary(boss);

    public static void editSalary(Employee x){
        x.raiseSalary(200);
        System.out.println("方法結束後的Salary:"+x.getSalary());
    }

第一段程式碼,其執行過程如下:
(1)x被初始化為percent的一個拷貝(10)
(2)x被乘3,但是percent不變
(3)方法結束後,引數變數x不再使用。

第二段程式碼,其執行過程如下:
(1)x被初始化為boss的拷貝,是一個物件的引用
(2)editSalary應用於這個物件的引用,這時x修改了這個物件,這時boss也引用同一個物件,這個物件的salary被提高了200%
(3)方法結束後,引數變數x不再使用,boss繼續引用已經被修改的Employee物件。

可以發現,當傳遞一個物件引數時,java對其的操作時近似引用傳遞的。這也是引發爭論的關鍵點。事實上,儘管《java核心技術》中闡述java不存在引用傳遞,依然有許多人認為java中存在引用傳遞。以下是一個極易引發爭論的程式。

public class Example {
    String str = new String("good");
    char[] ch = { 'a', 'b', 'c' };
    public static void main(String args[]) {
        Example ex = new Example();
        //執行了這一句之後
        ex.change(ex.str, ex.ch);
        //ex的str屬性沒變,但是ch屬性被修改了
        System.out.print(ex.str + " and ");
        System.out.print(ex.ch);
    }

    public void change(String str, char ch[]) {
        str = "test ok";
        ch[0] = 'g';
    }
}

這段程式輸出了什麼呢?

good and gbc      ch被方法修改了而str沒有被修改。

很明顯,str和ch[]都是物件傳遞,傳遞到方法中的是一個引用了物件的拷貝。那麼為什麼char[]被修改了而String沒有被修改呢。
問題出在

str="test ok"

字串由於其特殊性,實際上方法中的拷貝物件str在執行完str=”test ok”後,就已經指向了test ok,而原來的ex物件依然指向good,並沒有受到影響。
而char[]物件的拷貝,直接修改了char[0]儲存的內容,這時ex物件的屬性和方法拷貝依然指向同一個物件,因此當這個物件被方法拷貝修改後,ex受到了影響。

總結起來,java中引數傳遞情況如下:
· 一個方法不能修改一個基本資料型別的引數
· 一個方法可以修改一個物件引數的狀態
· 一個方法不能實現讓物件引數引用一個新物件

下面使用《java核心技術》的一段程式碼解釋為什麼java中不存在引用傳遞:編寫一個交換物件的方法:

Employee a=new Employee("tom", 7000);
Employee b=new Employee("jerry", 4000);

public static void swap(Employee x,Employee y){
    //交換兩個員工的姓名
    Employee temp=x;
    x=y;
    y=temp;
}

如果java使用的是引用傳遞,那麼swap方法應該可以實現交換兩個物件值的效果,然而最終結果是,swap並沒有改變儲存在a和b中的物件引用。swap方法的引數x,y被初始化為兩個物件引用的拷貝,這個方法交換的是兩個拷貝,最終沒有實現交換的結果。

總結:儘管在《核心技術》中有明確的表述java使用的是值傳遞,但是java在給方法傳遞一個物件引數時,具體的行為已經很難界定是值傳遞還是引用傳遞了。畢竟,如果從行為上來界定,很難說傳遞物件引數不符合引用傳遞的定義。

因此,如果開篇題目是一道簡答題,你可以把自己對java引數傳遞的理解從容的描述清楚,相信面試官會給你一個滿意的分數。如果這道題被做成一個選擇題,不同的理解方式會有不同的答案,只能說明面試官自己準備都不充分,就算答得不對,也無關緊要。這樣的公司,不去就不去了,沒啥好遺憾的。