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的值,並沒有發生變化。為什麼呢?實際上這個執行的過程如下:
- 定義變數v,給v分配一塊記憶體,記憶體中的值存放5
- 呼叫changeValue方法,分配一塊記憶體給形參value,並將v的值拷貝到value的記憶體中
- 執行方法,將value記憶體中的值加4,變成9,但是v的記憶體存放仍然是5
- 方法結束,釋放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());// 結果輸出 西施 } }
前面我們說過值呼叫不會改變形參的值,但是這裡好像把貂蟬的名字改成西施了,為啥呢?我們先分析下執行過程:
- 定義變數diaochan並構造一個美人物件賦值給它,給diaochan分配一塊記憶體,同時在堆記憶體中分配空間存放美人物件。變數diaochan記憶體中的存放的是美人物件的地址,假設地址為0xA1
- 呼叫changeName方法,分配一塊記憶體給形參player,並將diaochan的值拷貝到player的記憶體中,因此形參player的值也為0xA1,指向美人物件
- 執行changeName方法,呼叫形參player的修改器setName方法,實際上就是呼叫美人物件的setName方法,因此美人物件的名字變成“西施”。
- 方法結束,釋放形參player記憶體,實參diaochan和美人物件的記憶體依然存在
我們也用一張圖來演示:
我們看到自始至終,實參diaochan記憶體中的值一直沒變,都是0xA1。因為美人物件的名字變了,因此有的網文甚至有的書籍說Java類型別是引用呼叫,筆者認為是屬於錯誤的說法。因為看是否是值呼叫,根本是要看是否傳遞的是實參記憶體的值,Java中類型別的傳遞,也是傳遞的實參記憶體中的值,只不過這個值是一個物件的地址(即引用)。