1. 程式人生 > 實用技巧 >java基礎06 Java是值傳遞還是引用傳遞?

java基礎06 Java是值傳遞還是引用傳遞?

  答案:Java沒有引用傳遞。
  很多人看到這個答案可能會很苦惱,可能會說博主傻缺,怎麼會沒有引用傳遞呢。各位看官,請稍安勿躁,且聽我慢慢道來。

基本概念

  在道出原因之前,我們先了解一下基本概念,以便初學者或基礎不太紮實的看官有一個基本的認識。

  • 實參:指實際引數,是提前準備好的引數用於傳入方法中。儲存在棧記憶體中;若實參是基本型別,存的是基本型別的值;若實參是引用型別,存的是實參的引用,用於指向堆中的物件。
  • 形參:指形式引數,方法中的引數。方法呼叫是在棧中取儲存的對應實參副本。
  • 值傳遞:方法呼叫的時候,實參把它的值傳遞給對應的形參,方法內對形參的改變不會影響到實參的值。
  • 引用傳遞:方法呼叫的時候,實參將它的地址傳遞給形參,方法內對形參值的改變,會影響到實參的值。
/**
 * 實參和形參
 */
public class ParameterConcept {

    public static void main(String[] args) {
        int actualParameter = 1;
        print(actualParameter);		// 這裡傳遞的actualParameter 是實參
        System.out.println("actualParameter的值:" + actualParameter);
    }

    public static void print(int shapeParameters){	// 這裡的 shapeParameters 表示形參
        shapeParameters = 10;
        System.out.println("shapeParameters的值:" + shapeParameters);
    }
}

這個就是典型的值傳遞,其輸出結果如下:

shapeParameters的值:10
actualParameter的值:1

求值策略

當我們進行方法的呼叫的時候,需要把實參傳遞給形參,那我們傳遞的究竟是什麼東西?
我們先來理解一個概念:求值策略(Evaluation strategy)。在百度百科是這樣解釋的:

  • 在電腦科學中,求值策略(Evaluation strategy)是確定程式語言中表達式的求值的一組(通常確定性的)規則。重點典型的位於函式或運算元上——求值策略定義何時和以何種次序求值給函式的實際引數,什麼時候把它們代換入函式,和代換以何種形式發生。
  • 求值策略分為兩大基本類,嚴格的和非嚴格的,基於如何處理給函式的實際引數。

我們通俗的理解就是,計算機對值有一種策略,這種策略是用來指定求值的順序、值傳遞的時機以及傳遞的方式。

嚴格求值

  在“嚴格求值”中,函式呼叫過程中,給函式的實際引數總是在應用這個函式之前求值。多數現存程式語言對函式都使用嚴格求值。所以,我們本文只關注嚴格求值。
  在嚴格求值中有幾個關鍵的求值策略是我們比較關心的,那就是傳值呼叫(Call by value)、傳引用呼叫(Call by reference)以及傳共享物件呼叫(Call by sharing)。

上面對值傳遞(傳值呼叫)和引用傳遞(傳引用呼叫)做了簡單的解釋,這裡再做更詳細一點的解釋。

  1. 傳值呼叫(值傳遞):在傳值呼叫的過程中,實參會被進行一次求值操作,然後把求到的值拷貝一份傳遞給形參。方法中是對形參進行操作(也就是實參值的一個副本),所以不會影響到實參的值。
  2. 傳引用呼叫(引用傳遞):在傳引用呼叫的過程中,傳遞給方法是實參的一個隱式引用,不是實參值的副本,所以對形參的操作會影響到實參的值。
  3. 傳共享物件呼叫(共享物件傳遞):在傳共享物件呼叫的過程中,對實參進行求值操作,會獲取實參的儲存地址(引用指向的地址),對這個地址進行拷貝一份再傳遞給形參,形參獲取的地址與實參的地址是指向同一個位置,所以對形參的操作會影響到實參的值。

  到這裡,大家應該能發現,傳共享物件呼叫傳值呼叫很相似,都是對實參進行求值、拷貝、傳遞三個主要操作,唯一不同的是一個傳遞的是指向真實值的地址,一個傳遞的是真實值的副本。
  傳共享物件呼叫可以理解為值傳遞與引用傳遞的結合版,它有著值傳遞類似的操作過程和引用傳遞方式的結果。

三種傳遞的圖示

值傳遞

引用傳遞

傳共享物件呼叫

public class Transmit {

    public static void main(String[] args) {
        Student student = new Student("小明", 23);
        change(student);
        System.out.println("學生的年齡:" + student.getAge());
    }

    private static void change(Student student){
        student.setAge(80);
    }

    @Data
    @AllArgsConstructor
    static class Student{
        private String name;
        private int age;
    }
}

顯示結果為:

學生的年齡:80

  從顯示的結果我們可以看出,傳遞的物件是一個引用型別(資料型別有兩種,下面會給出),學生的年齡經過 change 方法之後,變成了 80 ,所以肯定不是值傳遞,那它是引用傳遞還是傳共享物件呼叫呢?
  由於引用傳遞和共享物件傳遞最終的執行效果是一樣的,都會影響到呼叫者的值,就很難判斷,在翻閱很多部落格的時候,在一篇部落格中是這樣記載著。
  在 《The Java™ Tutorials》中,是有關於這部分內容的說明的。首先是關於基本型別描述如下:

Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.

  即,原始引數通過值傳遞給方法。這意味著對引數值的任何更改都只存在於方法的範圍內。當方法返回時,引數將消失,對它們的任何更改都將丟失。

  關於物件傳遞的描述如下:

Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object’s fields can be changed in the method, if they have the proper access level.

  也就是說,引用資料型別引數(如物件)也按值傳遞給方法。這意味著,當方法返回時,傳入的引用仍然引用與以前相同的物件。但是,如果物件欄位具有適當的訪問級別,則可以在方法中更改這些欄位的值。

  這些引用描述都是來自於官方文件,Java就是值傳遞,只不過是把物件的引用當做值傳遞給方法,這就很符合共享物件傳遞的定義了。
  其實Java中使用的求值策略就是傳共享物件呼叫,也就是說,Java會將物件的地址的拷貝傳遞給被調函式的形式引數。只不過"傳共享物件呼叫"這個詞並不常用,所以Java社群的人通常說"Java是傳值呼叫",這麼說也沒錯,因為傳共享物件呼叫其實是傳值呼叫的一個特例。

資料型別

資料型別可分為兩大類:

  • 基本型別:Java的八大基本型別。
  • 引用型別:物件,String等八大基本型別的封裝類。

總結

我們知道,程式語言中需要進行方法間的引數傳遞,這個傳遞的策略叫做求值策略。

在程式設計中,求值策略有很多種,比較常見的就是值傳遞和引用傳遞。還有一種值傳遞的特例——共享物件傳遞。

值傳遞和引用傳遞最大的區別是傳遞的過程中有沒有複製出一個副本來,如果是傳遞副本,那就是值傳遞,否則就是引用傳遞。

在Java中,其實是通過值傳遞實現的引數傳遞,只不過對於Java物件的傳遞,傳遞的內容是物件的引用。

我們可以總結說,Java中的求值策略是共享物件傳遞,這是完全正確的。

但是,為了讓大家都能理解你說的,我們說Java中只有值傳遞,只不過傳遞的內容是物件的引用。這也是沒毛病的。

但是,絕對不能認為Java中有引用傳遞。