首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

關於 Java 參數傳遞的終極解惑:值傳遞還是引用傳遞?

     編輯:Java 綜合教學

關於 Java 參數傳遞的終極解惑:值傳遞還是引用傳遞?

「Java 是值傳遞還是引用傳遞?」這個問題困擾了無數開發者,甚至引發激烈筆戰。本文從 JVM 內存模型、語言規範權威定義、字節碼執行、實戰驗證四個維度,給出鐵板釘釘的結論,並澄清常見誤區,徹底終結這場爭論。

權威結論:Java 只有值傳遞

《Java 核心技術 卷 I》Horstmann 原文:
「Java 程式設計語言總是採用值調用。也就是說,方法得到的是所有參數值的一個拷貝,特別是,方法不能修改傳遞給它的任何參數變量的內容。」

Java Language Specification (JLS) §8.4.1:
「當方法被調用時,每個參數變量被創建並初始化為對應實參表達式的值。」

結論:Java 嚴格採用按值傳遞。無例外、無特殊情況、無「對象引用傳遞」這一說法。

為什麼會產生「引用傳遞」錯覺?

因為引用本身也是值。當傳遞對象時,拷貝的是引用的值(記憶體地址),而非對象本身。這導致:

  • 方法內參數與實參指向同一個堆對象
  • 可通過參數修改對象內部狀態(欄位、集合元素)
  • 但無法修改實參變量本身的指向(即無法讓實參指向新對象)

JVM 內存模型視角:棧幀與堆對象

// 實參在呼叫者棧幀
StringBuilder sb = new StringBuilder("hello"); // sb 在棧,對象在堆

// 被調用者棧幀創建參數變量
void appendExclamation(StringBuilder param) {
    param.append("!");      // 修改堆對象內容 → 可見
    param = new StringBuilder("world"); // 修改參數變量指向 → 僅影響局部變量
}

圖解:

  • 調用前:sb → [heap: "hello"],param 未存在
  • 調用時:param = sb(拷貝引用值),雙雙指向同一堆對象
  • append("!"):堆對象變為 "hello!",雙雙可見
  • param = new ...:param 指向新對象,sb 仍指向舊對象
  • 返回:param 隨棧幀銷毀,sb 不變

四大經典驗證案例

案例 1:基本類型 —— 純值拷貝

int a = 10;
modify(a);
System.out.println(a); // 10,不變

void modify(int x) { x = 20; }

案例 2:對象引用 —— 修改內部狀態可見

User u = new User("Alice");
changeName(u);
System.out.println(u.getName()); // "Bob",變了!

void changeName(User user) {
    user.setName("Bob"); // 修改堆對象欄位
}

案例 3:對象引用 —— 重新賦值不可見

User u = new User("Alice");
replace(u);
System.out.println(u.getName()); // "Alice",沒變!

void replace(User user) {
    user = new User("Bob"); // 只改參數指向
}

案例 4:String 不可變性 —— 看似「值傳遞」實則引用拷貝

String s = "hello";
modifyString(s);
System.out.println(s); // "hello",不可變對象無法修改內容

void modifyString(String str) {
    str = str + " world"; // 產生新字串,str 指向新對象
}

常見誤區粉碎機

誤區真相
「對象是引用傳遞,基本類型是值傳遞」錯。統統是值傳遞,對象傳的是引用的值。
「能修改對象內容就是引用傳遞」錯。修改的是共享堆對象,而非參數變量本身。引用傳遞定義是「可修改實參變量指向」。
「String 是特殊的值傳遞」錯。String 同樣傳引用值,只是不可變導致無法修改內容,重新賦值同樣只影響局部參數。
「Swap 寫不出來證明是引用傳遞」錯。Swap 無法實現正是值傳遞的鐵證:無法修改實參變量指向。

為什麼這區分很重要?

  1. API 設計:文檔明確「修改傳入對象狀態」vs「返回新對象」,避免調用方誤解。
  2. 防禦性編程:集合/陣列參數若不想被修改,需防禦性複製(new ArrayList<>(list))或用不可變視圖(Collections.unmodifiableList())。
  3. 並發安全:多線程共享可變對象需同步/不可變模式,參數傳遞機制不改變共享本質。
  4. 面試核心考點:精準表述「值傳遞、引用值拷貝、堆對象共享、棧幀隔離」即得滿分。

總結:一句話記住一輩子

Java 永遠是值傳遞。傳對象時,拷貝的是引用(地址)這個值。方法可修改共享對象內容,但絕不能改變調用方變量的指向。

理解這一點,不僅能終結爭論,更能指導你寫出語義清晰、副作用可控、並發安全的 Java 代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved