Java 陣列是 call by address?Python 是 call by reference?兩個常見誤解一次釐清
Java 陣列是 call by address?Python 是 call by reference?兩個常見誤解一次釐清

剛開始教初學者時,我很常看到同一種答案反覆出現:
- Java 陣列傳進方法後可以改到內容,所以它是 call by address
- Python 的 list 傳進函式後也能改到外面的值,所以它是 call by reference
這兩句話都很像對的,因為從執行結果看起來,真的很像「我把東西交進去,裡面改了, 外面也跟著變」。但也正因為「看起來很像」,但又似乎看過相異的解釋,深怕錯誤的解釋會影響學習效果。
這篇我從錯誤認知出發,借助 AI 詢問釐清自己的觀念,最後希望能回到官方真正的說法。
上述兩個說法都不夠精確

Java
不是 call by address,而是 pass by value。只是當你傳的是物件或陣列時,傳進去的那個「值」,是參考值的副本。
Python
Python 官方也不把它叫做 call by reference。Python FAQ 的說法是 arguments are passed by assignment,而且明白寫到 there's no ... call-by-reference。
所以,如果要用一句最不容易誤導學生的話來說:
Java 一律是傳值;Python 不是 call by reference。
為什麼這麼多人會學成錯的?

因為下面這種程式,真的很容易讓人誤會。
Java 範例
public class Main {
public static void change(int[] arr) {
arr[0] = 100;
}
public static void main(String[] args) {
int[] nums = {1, 2, 3};
change(nums);
System.out.println(nums[0]);
}
}
輸出:
100
Python 範例
def change(arr):
arr[0] = 100
nums = [1, 2, 3]
change(nums)
print(nums[0])
輸出:
100
學生看到這裡,第一個反應通常都很合理:
「都改得到外面,那不就是 reference 嗎?」
這個推論很自然,但問題就出在這裡:「改得到同一個物件內容」不等於「呼叫端變數本身是被 reference 傳進去」。
先建立一個基本觀念:變數不是在搬整個陣列

最容易卡住的地方,是腦中會把變數想成「盒子裡直接裝著整個陣列」。
但比較接近實際的理解是:
- 變數手上拿的是一張「位置紙條」
- 紙條上寫著:那個陣列 / list 在哪裡
- 所以變數不是整個陣列本身,而是找到它的方式

可以先想成這樣:
nums ───► [1, 2, 3]
在 Java 裡,這種理解格外重要,因為官方規格直接寫到:arrays are objects。也就是說,陣列本身就是物件。
Java 官方原文: "In the Java programming language, arrays are objects..." — JLS Chapter 10
釐清認知一:Java 陣列是 call by address ?

這是常看到的說法,也是在教學時對照文獻後想要釐清的根本觀念。Java 官方教學的英文原意很直接:
"Reference data type parameters, such as objects, are also passed into methods by value." — Oracle Java Tutorials
這句話很重要,原文比中文更能解釋,代表當你呼叫:
change(nums);
Java 不是把 nums 這個變數本人交進去,而是把 nums 裡面保存的那個參考值複製一份,交給方法參數 arr。
所以比較像這樣:
main 內 change 內
nums ───► [1, 2, 3] ◄─── arr
因為 nums 和 arr 都指向同一個陣列,所以你在方法裡做:
arr[0] = 100;
改到的是同一個陣列內容,外面當然也會看到變化。
這也是為什麼學生會覺得「很像 reference」,但官方還是堅持它叫 by value:因為被複製的是參考值,不是外面那個變數本身。
如何證明 Java 不是 call by address?

我們直接看第二個例子。
public class Main {
public static void reset(int[] arr) {
arr = new int[]{9, 9, 9};
}
public static void main(String[] args) {
int[] nums = {1, 2, 3};
reset(nums);
System.out.println(nums[0]);
}
}
輸出:
1
如果 Java 真的是「外面的變數直接被交進方法裡」,那 arr = new int[]{9,9,9}; 之後,外面的 nums 理論上也應該一起改掉。
但它沒有。
原因就在於:
arr拿到的只是nums的參考值副本- 接著把
arr改去指向新陣列 - 改到的只有方法裡自己的
arr - 外面的
nums還是原本那個陣列
圖解會更清楚:
原本:
nums ───► [1, 2, 3] ◄─── arr
方法內重新指定後:
nums ───► [1, 2, 3]
arr ───► [9, 9, 9]
也就是說,Java 陣列傳參數時真正發生的是:
- 可以透過參考值副本,改到同一個物件內容
- 但不能直接改掉呼叫端變數的綁定
這就是 Java 官方為什麼堅持它是 pass by value。
釐清認知二:Python 就是 call by reference
這個
誤解也非常普遍,原來的我也無法很精確地釐清,尤其對於學過 C、C++ 之後再來看 Python,更容易直接套上 call by reference 這個詞。
但 Python 官方 FAQ 寫得更直接:
"Remember that arguments are passed by assignment in Python."
而且同一段後面還有一句更關鍵的:
"Since assignment just creates references to objects, there's no alias between an argument name in the caller and callee, and consequently no call-by-reference." — Python FAQ
這句話到底在講什麼?
意思是:
- 函式參數只是多了一個名字
- 這個名字一開始也指向同一個物件
- 但它不是外面那個變數本身的別名
- 所以 Python 不把它叫做 call-by-reference
Python 為什麼也會讓人誤會?
先看一個大家都很熟的例子:
def change(arr):
arr[0] = 100
nums = [1, 2, 3]
change(nums)
print(nums[0])
輸出:
100
這時候可以想成:
nums ───► [1, 2, 3] ◄─── arr
因為 nums 和 arr 都指向同一個 list,所以改 arr[0] 時,當然也就改到了原本那個 list 內容。
但真正的關鍵,是再看另一個例子。
def reset(arr):
arr = [9, 9, 9]
nums = [1, 2, 3]
reset(nums)
print(nums[0])
輸出:
1
這裡和 Java 幾乎是同樣的觀察:
arr = [9, 9, 9]只是把函式裡的arr改成指向另一個新物件- 外面的
nums完全沒有被重新綁定 - 所以外面還是原本那個
[1, 2, 3]
圖解:
原本:
nums ───► [1, 2, 3] ◄─── arr
重新指定後:
nums ───► [1, 2, 3]
arr ───► [9, 9, 9]
所以 Python 的問題不是「它很像 reference」,而是「它常常讓你誤以為參數和外面變數是同一個東西」。官方才會特別說:no call-by-reference。
