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

Java 繼承陷阱深度解析:欄位隱藏與多態方法派發的經典面試題

     編輯:Java 相關知識

前言

CSDN 上流傳一道經典繼承題目,涉及欄位隱藏與方法覆寫的交互行為。本文透過記憶體模型與 JVM 派發機制,徹底釐清輸出結果背後的原理。

程式碼重現

public class Test2 {
    public String user = "test2";

    public String getUser() {
        System.out.println(this.user); // (1)
        user = getPassword();          // (2)
        return user;
    }

    public String getPassword() {
        System.out.println(this.user);
        return "hello test2";
    }
}

public class Test3 extends Test2 {
    public String user = "test3";

    public String getPassword() {
        System.out.println(this.user);
        return "hello test3";
    }
}

public class Test4 extends Test3 {
    public String user = "test4";

    public static void main(String[] args) {
        Test4 t4 = new Test4();
        t4.getUser();
    }
}

執行輸出

test2
test3

核心解析

1. 欄位隱藏 — 非多態

  • Java 欄位不參與多態,子類別宣告相同名稱欄位會 隱藏 父類別欄位,而非覆寫。
  • Test4 物件在記憶體中實際包含三個 user 欄位:
    Test2.user="test2", Test3.user="test3", Test4.user="test4"。
  • 存取規則:編譯期根據 引用型別 決定存取哪個欄位。
    • 在 Test2 類別程式碼中寫 this.user → 編譯綁定 Test2.user。
    • 在 Test3 類別程式碼中寫 this.user → 編譯綁定 Test3.user。

2. 方法覆寫 — 執行期動態派發

  • getPassword() 在 Test3 被覆寫,Test4 未覆寫繼承 Test3 版本。
  • 呼叫 t4.getUser() 時:
    ① Test2.getUser() 執行 System.out.println(this.user) → 印出 Test2.user = test2。
    ② 呼叫 getPassword() → 動態派發至 Test3.getPassword() (最近覆寫者)。
    ③ Test3.getPassword() 執行 System.out.println(this.user) → 印出 Test3.user = test3。
    ④ 回傳 "hello test3" 給 Test2.getUser() 的 user 區域變數 (實際賦值給 Test2.user 欄位)。

3. 除錯器顯示「test4」的原因

IDE 除錯器 (如 JBuilder) 顯示的是 物件實際記憶體佈局,依欄位宣告順序列出所有欄位,最後一個同名欄位 (Test4.user) 視覺上最明顯,誤以為 this.user 指向它。實際執行時仍遵循編譯期綁定規則。

記憶體佈局示意圖

Test4 物件實例
+------------------+
| Test2.user = "test2"  ← Test2 程式碼存取此欄位
| Test3.user = "test3"  ← Test3 程式碼存取此欄位
| Test4.user = "test4"  ← Test4 程式碼存取此欄位
| ... 其他繼承欄位 ...
+------------------+
方法表
Test2.getUser()    → 父類實作
Test3.getPassword() → 子類覆寫 (Test4 繼承此版本)
Test4.getUser()    → 繼承 Test2 版本

最佳實踐建議

  1. 避免欄位隱藏:子類別勿宣告與父類別同名欄位;若需不同值,改用 protected 方法 getUser() 封裝。
  2. 欄位私有化:改用 private + getter/setter,徹底消除隱藏風險。
  3. 理解編譯期 vs 執行期綁定:欄位 = 編譯期靜態綁定;方法 = 執行期動態派發。

延伸思考

若在 Test3 也覆寫 getUser() 並呼叫 super.getUser(),輸出會如何變化?請嘗試自行驗證。

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