繼承是 Java 核心特性,但初始化順序往往讓開發者困惑,尤其是涉及靜態成員、實例塊、構造器、向上轉型時。本文通過完整案例,系統梳理執行流程。
// 父類
public class TestStatic {
public static String name = "china";
{ System.out.println("========父類實例塊========"); }
static {
name = "England";
System.out.println("========父類靜態塊======");
}
TestStatic() {
System.out.println("========父類構造器========");
}
public static void main(String[] args) {
System.out.println("========父類main========" + name);
}
public void test() {
System.out.println("========父類test========");
}
}
// 子類
public class TestExtendStatic extends TestStatic {
// public static String name = "HUBEI";
{ System.out.println("========子類實例塊========"); }
static {
// name = "SUIZHOU";
System.out.println("========子類靜態塊======");
}
TestExtendStatic() {
System.out.println("========子類構造器========");
}
@Override
public void test() {
System.out.println("========子類test========");
}
public static void main(String[] args) {
System.out.println("========子類main========" + name);
TestStatic ts = new TestExtendStatic(); // 向上轉型
ts.test();
}
}
========父類靜態塊====== ← 1. 父類靜態字段/靜態塊(按定義順序)
========子類靜態塊====== ← 2. 子類靜態字段/靜態塊
========子類main========England ← 3. 子類 main 執行,打印繼承的 name="England"
========父類實例塊======== ← 4. new 子類:父類實例字段/實例塊
========父類構造器======== ← 5. 父類構造器
========子類實例塊======== ← 6. 子類實例字段/實例塊
========子類構造器======== ← 7. 子類構造器
========子類test============ ← 8. 多態調用:實際執行子類重寫的 test()
new Child() 觸發:父類實例字段/塊 → 父類構造器 → 子類實例字段/塊 → 子類構造器super(),形成遞歸鏈,父類構造器最先完成靜態塊只執行一次(類加載時),實例塊與構造器每次 new 都執行。
TestStatic ts = new TestStatic(); // 不加載子類靜態塊!
ts.test();
輸出:父類靜態塊 → 父類實例塊 → 父類構造器 → 父類 test()。
若在父類 main 中執行,子類靜態塊不會加載。
TestStatic ts = new TestExtendStatic(); // 引用類型是父類,對象類型是子類
TestExtendStatic ts2 = new TestExtendStatic(); // 引用類型是子類
兩種寫法觸發的初始化鏈完全一致。區別僅在編譯期可見成員與運行期多態調用。
先靜後動,先父後子,按序定義,構造收尾。多態調用,對象完全初始化後方生效。