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
Test4 物件在記憶體中實際包含三個 user 欄位:Test2.user="test2", Test3.user="test3", Test4.user="test4"。Test2 類別程式碼中寫 this.user → 編譯綁定 Test2.user。Test3 類別程式碼中寫 this.user → 編譯綁定 Test3.user。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 欄位)。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 版本
protected 方法 getUser() 封裝。private + getter/setter,徹底消除隱藏風險。若在 Test3 也覆寫 getUser() 並呼叫 super.getUser(),輸出會如何變化?請嘗試自行驗證。