1. 介紹
在各種程式語言中,將參數傳入函式(或方法)主要有兩種方式:
- 值傳遞:函式收到的是變數值的副本。若函式改變參數,原始變數不會改變。
- 參考傳遞:函式接收原始物件的「參考」(指標)。在函式內的變更會反映到原始物件。
在 Java 中,所有參數一律是值傳遞!
但有個眉角:當我們傳遞基本型別(int、double、boolean 等)時,複製的是值本身。而當傳遞參考型別(例如陣列或物件)時,複製的是參考的值(也就是記憶體中物件的「位址」),而不是物件本身。
如果你覺得腦袋開始融化,別擔心——我們現在就條理化並用範例來說明!
2. 基本型別:複製的是值
先從簡單的開始:如果我們試著在方法內改變 int 型別變數的值,會發生什麼事?
public class Demo
{
public static void main(String[] args)
{
int number = 5;
changeValue(number);
System.out.println(number); // 會印出什麼?
}
public static void changeValue(int n)
{
n = 42;
}
}
結果:畫面會印出5。
為什麼?
傳入 changeValue 方法的是變數 number 的值的副本。在方法內我們只改到副本(n),位於 main 的原始變數 number 不會被動到。
比喻
想像你給了朋友護照的影本,而不是正本。朋友愛怎麼在影本上畫鬍子都行——你的真正護照還是原樣!
3. 參考型別:複製的是參考
現在用陣列試試看:
public class Demo
{
public static void main(String[] args)
{
int[] numbers = {1, 2, 3};
changeFirst(numbers);
System.out.println(numbers[0]); // 會印出什麼?
}
public static void changeFirst(int[] arr)
{
arr[0] = 99;
}
}
結果:畫面會印出99。
為什麼?
傳入 changeFirst 方法的是指向陣列 numbers 的參考的副本。兩個名稱(main 中的 numbers 與方法裡的 arr)都指向記憶體中的同一個陣列。因此在方法內改變陣列元素,外面也「看得到」。
視覺化
main: numbers ──► [1, 2, 3]
▲
│
changeFirst: arr ──┘
當我們改變 arr[0],其實就是在改 numbers[0],因為兩者都指向同一個物件。
4. 但如果改變參考本身——原來的物件不會改變!
來試試看這樣做:
public class Demo
{
public static void main(String[] args)
{
int[] numbers = {1, 2, 3};
replaceArray(numbers);
System.out.println(numbers[0]); // 會印出什麼?
}
public static void replaceArray(int[] arr)
{
arr = new int[] {10, 20, 30};
}
}
結果:會印出1。
為什麼?
在 replaceArray 方法裡,變數 arr 轉而指向新陣列,但這不會影響 main 中的變數 numbers。
它們原本共用同一個參考,不過當我們在方法內指派 arr = ... 時,這只是區域性的變更。
比喻
這就好比你和朋友原本拿的是同一把房門鑰匙。朋友另外配了一把新鑰匙,但你手上的舊鑰匙仍然不變,門也還是同一扇。
5. 加深練習:無法透過方法把兩個 int「互換」
許多新手嘗試撰寫一個把兩個數字交換的位置的方法,但結果行不通:
public class Demo
{
public static void main(String[] args)
{
int a = 5, b = 10;
swap(a, b);
System.out.println(a + " " + b); // 預期:10 5,那實際呢?
}
public static void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
}
結果:畫面會印出5 10。
為什麼?
因為進到 swap 的,是變數 a 與 b 的副本。我們在 swap 裡做的一切,只會影響這些副本,而不是原始變數。
6. 但對陣列可以改變其內容!
來看用陣列正確地交換數值的方法:
public class Demo
{
public static void main(String[] args)
{
int[] arr = {5, 10};
swap(arr);
System.out.println(arr[0] + " " + arr[1]); // 預期:10 5
}
public static void swap(int[] arr)
{
int temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
}
結果:會印出10 5。
為什麼?
因為我們改的是物件(陣列)的內容,而呼叫端的原始變數與方法參數都指向同一個物件。
7. 示範:物件與其欄位
陣列說明完了,那自訂類別的物件呢?完全一樣!
class Box
{
int value;
}
public class Demo
{
public static void main(String[] args)
{
Box box = new Box();
box.value = 7;
changeBox(box);
System.out.println(box.value); // 會印出什麼?
}
public static void changeBox(Box b)
{
b.value = 42;
}
}
結果:會印出42。
為什麼?
因為我們改的是參考所指向物件的欄位。
將參數傳入方法時會發生什麼
| 傳入的是 | 複製的是 | 可在方法內改變嗎? | 外部看得到變更嗎? |
|---|---|---|---|
| int、double 等 | 值的副本 | 可以,但只能改到副本 | 不會 |
| 陣列 | 參考的副本 | 可以,若變更元素 | 會 |
| 物件(類別) | 參考的副本 | 可以,若變更欄位 | 會 |
| 陣列/物件 | 參考的副本 | 不會,若只是指派新的參考 | 不會 |
8. 常見錯誤
錯誤 1:期待 int 在呼叫方法後會改變。 許多新手以為把 int 變數傳進方法後,就能在方法裡改掉它的值。這是錯的:改到的只有副本!
錯誤 2:嘗試用方法把兩個 int 互換。 如我們已經看到的,這做不到——只有變數的副本在互換,原始變數不受影響。
錯誤 3:嘗試透過方法「替換」陣列。 如果你在方法內把參數陣列指派成新的陣列(arr = new int[]{...}),這不會影響原本的陣列。若要「替換」陣列——需要由方法回傳新的陣列,並在呼叫端把它指派給變數。
錯誤 4:忘了可以改物件的內容。 若你傳的是物件(或陣列),別忘了:對其欄位/元素的任何變更都會在外面看得到!
錯誤 5:與 null 搞混。 如果傳進方法的參考是 null,還嘗試存取其欄位——就會得到 NullPointerException。請小心!
GO TO FULL VERSION