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

Java 繼承中父類引用子類對象調用被覆蓋方法的行為分析

     編輯:程式設計綜合問答

問題場景

class Father {
    void methodA() { System.out.println("Father.A"); }
    Son sonRef = new Son(); // 父類持有子類實例引用
    void callSonA() { sonRef.methodA(); }
}

class Son extends Father {
    @Override void methodA() { System.out.println("Son.A"); }
}

問題:在 `Father` 中調用 `sonRef.methodA()` 時,執行的是 `Father.A` 還是 `Son.A`?

核心結論:多態生效,執行子類方法

  • **動態綁定**:Java 非靜態方法調用基於**對象實際類型**(運行時類型)分發,而非引用聲明類型。
  • `sonRef` 編譯類型為 `Son`,運行時指向 `Son` 實例,調用 `methodA()` 會查找 `Son` 的方法表,執行 `Son.A`。
  • 即使調用代碼寫在 `Father` 類中,多態機制不變。

最佳回答深度解析:構造器調用鏈中的陷阱

「假如你實例化 son,jvm 會根據繼承關系先調用父類構造函數,但是在調用所有構造函數之前會將每個類的對象初始化成二進制的 0,所以父類任然會調用子類的方法,但是結果完全不可預料,所以強烈建議不要這樣寫。」

構造器中調用被覆蓋方法的風險

class Father {
    Father() {
        init(); // 構造器中調用可覆蓋方法
    }
    void init() { System.out.println("Father.init"); }
}

class Son extends Father {
    String name = "Son"; // 實例變量初始化
    Son() {
        super(); // 隱式調用 Father()
        System.out.println("Son constructor, name=" + name);
    }
    @Override void init() { 
        System.out.println("Son.init, name.length=" + name.length()); // 此時 name 尚未初始化!
    }
}

new Son(); 
// 輸出:
// Son.init, name.length=0  <-- name 為 null,拋出 NPE 或輸出 0
// Son constructor, name=Son

風險根源

  1. **初始化順序**:父類構造器執行時,子類實例變量**尚未初始化**(仍為默認值 0/null/false)。
  2. **多態提前生效**:父類構造器調用 `init()` 觸發動態分發,進入子類 `init()`,但子類字段未就緒。
  3. **不可預測**:導致空指針、邏輯錯誤、難以調試。

最佳實踐:禁止在構造器中調用可覆蓋方法

  • 將初始化邏輯移至**工廠方法**、**`init()` 顯式調用**或**構造器參數傳遞**。
  • 若必須在構造器中調用,將方法標記為 `final`、`private` 或 `static`(禁用多態)。
  • 使用 **檢查工具**:SpotBugs、SonarQube、IntelliJ Inspections 會標記「Constructor calls overridable method」。

總結

場景調用結果風險等級
構造器外調用 `sonRef.methodA()`執行 `Son.A`(正常多態)安全
父類構造器中調用 `this.methodA()`執行 `Son.A`,但子類字段未初始化極高危
父類構造器中調用 `sonRef.methodA()`同上,子類字段未初始化極高危

鐵律:構造器、最終化器、clone、反序列化等「對象不完全構建」階段,**絕不調用可覆蓋方法**。

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