《Java從入門到失業》第四章:類和物件(4.4):方法引數及傳遞

Java大失叔發表於2020-09-21

4.4方法引數及傳遞

       關於這個知識點,我想了很久該不該在這裡闡述。因為這個知識點稍微有點晦澀,並且就算不了解也不影響用Java編寫程式碼。不過筆者剛開始工作的時候,就是因為這塊內容沒有過多的關注,以至於相當於長一段時間對這塊內容都模糊不清甚至誤解。我相信你們都比我悟性高,因此決定還是先拿出來討論。

       我們知道,一個方法一般由修飾符、返回值、方法名和引數列表構成。這裡我們主要討論方法的引數。看一個例子:

  // 構造方法  
    public Mahjong(int type, int number) {  
        this.type = type;  
        this.number = number;  
    }  

這是麻將類的構造方法,有2個引數。我們看到引數由引數型別和引數名構成。引數型別可以是任何型別(即基本資料型別、類型別)。引數名需要滿足識別符號規範,一般建議使用有含義的名稱。因為方法將會作為API的一部分暴露給呼叫者閱讀,不要因為引數名的晦澀難懂而影響可讀性。

4.4.1形參和實參

       我們看一下構造一個麻將的程式碼:

int t= 1;  
int n = 2;  
Mahjong m = new Mahjong(t, n); 

形參:上面麻將構造方法中的引數type、number,我們稱之為形參,即形式引數。形參是定義方法的時候使用的引數,用來接收呼叫者傳遞的引數。方法在呼叫的時候,形參才會被分配記憶體空間,一旦方法呼叫完畢,形參的記憶體就會被釋放。

實參:這段程式碼中,我們先定義2個引數t和n,然後把t和n傳遞給麻將類的構造方法,t和n我們稱之為實參,即實際引數。實參是呼叫者傳遞給方法的引數,實參需要在呼叫之前賦值,即在方法呼叫之前就已經分配了記憶體空間,並且方法呼叫完畢之後記憶體不會釋放。用一張圖來示意:

 

4.4.2值呼叫和引用呼叫

       從上一小節我們看到,當呼叫方法的時候傳遞的是基本資料型別時,實際上是把實參的記憶體中的值傳遞給形參,這種方法呼叫我們稱之為“值呼叫”。

實際上,在程式語言中還有一種稱作“引用呼叫”的方式,例如C++同時存在值呼叫和引用呼叫兩種方式。引用呼叫是把實參記憶體地址傳遞給形參。注意和值呼叫的區別:

  • 值呼叫傳遞的是實參“記憶體的值”
  • 引用呼叫傳遞的是實參“記憶體的地址”

可能有的同學有點懵了,記憶體的值和記憶體的地址有什麼區別?回憶一下我們在第一章介紹記憶體的時候用來作比喻的蜂巢,蜂巢的每一個格子就相當於記憶體,它們都有一個唯一的編號,這就是記憶體地址,而格子裡存放的東西就是記憶體的值。只不過記憶體的地址和記憶體的值都是二進位制,因此容易混淆。

       事實上,在Java語言中,只有值呼叫一種方式,不管傳遞的是基本資料型別還是類型別。值呼叫因為傳遞的是記憶體的值,因此不管傳遞的是基本資料型別還是類型別,都不會改變實參記憶體中的值。我們先看一個基本資料型別的例子:

public class Method {  
    public static void changeValue(int value) {  
        value += 4;  
    }  
  
    public static void main(String[] args) {  
        int v = 5;  
        changeValue(v);  
        System.out.println(v);// 輸出結果是5,v的值沒有改變  
    }  
}  

我們看到,定義int變數v,然後傳遞給changeValue方法,方法內部把形參的值加4,但是對於實參v的值,並沒有發生變化。為什麼呢?實際上這個執行的過程如下:

  1. 定義變數v,給v分配一塊記憶體,記憶體中的值存放5
  2. 呼叫changeValue方法,分配一塊記憶體給形參value,並將v的值拷貝到value的記憶體中
  3. 執行方法,將value記憶體中的值加4,變成9,但是v的記憶體存放仍然是5
  4. 方法結束,釋放value記憶體

我們用一張圖來解釋:

 

我們再看一個傳遞類型別方法呼叫的程式碼:

我們先給美人類增加一個修改器方法:

public void setName(String name) {  
        this.name = name;  
    }  

然後,寫一個changeName方法,並呼叫,程式碼如下:

public class Method {  
  
    public static void changeName(Player player) {  
        player.setName("西施");  
    }  
  
    public static void main(String[] args) {  
        Player diaochan = new Player("貂蟬");  
        changeName(diaochan);  
        System.out.println(diaochan.getName());// 結果輸出 西施  
    }  
} 

前面我們說過值呼叫不會改變形參的值,但是這裡好像把貂蟬的名字改成西施了,為啥呢?我們先分析下執行過程:

  1. 定義變數diaochan並構造一個美人物件賦值給它,給diaochan分配一塊記憶體,同時在堆記憶體中分配空間存放美人物件。變數diaochan記憶體中的存放的是美人物件的地址,假設地址為0xA1
  2. 呼叫changeName方法,分配一塊記憶體給形參player,並將diaochan的值拷貝到player的記憶體中,因此形參player的值也為0xA1,指向美人物件
  3. 執行changeName方法,呼叫形參player的修改器setName方法,實際上就是呼叫美人物件的setName方法,因此美人物件的名字變成“西施”。
  4. 方法結束,釋放形參player記憶體,實參diaochan和美人物件的記憶體依然存在

我們也用一張圖來演示:

 

我們看到自始至終,實參diaochan記憶體中的值一直沒變,都是0xA1。因為美人物件的名字變了,因此有的網文甚至有的書籍說Java類型別是引用呼叫,筆者認為是屬於錯誤的說法。因為看是否是值呼叫,根本是要看是否傳遞的是實參記憶體的值,Java中類型別的傳遞,也是傳遞的實參記憶體中的值,只不過這個值是一個物件的地址(即引用)。

相關文章