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

Java 初始化陷阱:构造器中调用被覆盖方法导致字段未初始化

     編輯:Java 綜合教學

现象复现

Java 初始化顺序极其微妙:**父类构造器 → 子类字段显式初始化/实例代码块 → 子类构造器**。若父类构造器调用了被子类覆盖的方法,且该方法访问子类字段,会导致字段仍为默认值(null/0),而非预期的显式初始化值。

场景一:子类无显式构造器

class Base {
    Base() { preProcess(); } // 构造器调用可覆盖方法
    void preProcess() { }
}

class Derived extends Base {
    public String whenAmISet = "set when declared"; // 显式初始化
    void preProcess() { whenAmISet = "set in preProcess()"; } // 覆盖方法
}

public class StaticTest {
    public static void main(String[] args) {
        Derived d = new Derived();
        System.out.println(d.whenAmISet); // 输出: set when declared
    }
}

字节码视角还原真相

编译器将子类初始化逻辑插入构造器 <init> 中,**顺序为**:

  1. 调用 super()(父类构造器)。
  2. **父类构造器执行 `preProcess()`** → 此时子类字段 `whenAmISet` 尚未初始化(为 null),子类覆盖的 `preProcess()` 将其设为 `"set in preProcess()"`。
  3. **子类字段显式初始化** → `whenAmISet = "set when declared"`,**覆盖了步骤 2 的值**。
  4. 子类构造器体执行(无显式构造器则为空)。

等价代码:

class Derived extends Base {
    public String whenAmISet;
    { whenAmISet = "set when declared"; } // 实例初始化块,在 super() 之后、构造器体之前
    void preProcess() { whenAmISet = "set in preProcess(); }
}

结果:最终打印 `"set when declared"`,字段初始化「赢了」。

场景二:子类定义了构造器

class Derived extends Base {
    public String whenAmISet = "set when declared";
    public Derived() { whenAmISet = "set in constructor"; } // 显式构造器
    void preProcess() { whenAmISet = "set in preProcess()"; }
}
// 输出: set in constructor

字节码顺序变化

  1. super()
  2. 父类构造器调用 `preProcess()` → 设为 `"set in preProcess()"`
  3. **字段显式初始化** → 设为 `"set when declared"`
  4. **子类构造器体执行** → 设为 `"set in constructor"` **(最后执行,最终生效)**

结果:打印 `"set in constructor"`。

核心规律总结

阶段执行顺序场景一结果场景二结果
1. 父类构造器最先调用 preProcess() → set in preProcess调用 preProcess() → set in preProcess
2. 子类字段显式初始化/实例块第 2set when declared (最终)set when declared
3. 子类构造器体最后无set in constructor (最终)

最佳实践:如何避坑?

  1. 构造器中绝不调用可覆盖方法(包括直接或间接调用)。将方法标记为 final、private 或 static。
  2. 初始化逻辑下沉:在子类构造器中显式调用初始化方法,而非依赖父类构造器回调。
  3. 工厂模式/建造者模式:复杂对象创建交由静态工厂方法,构造器仅做字段赋值。

反编译验证命令

javac StaticTest.java
javap -c Derived.class  # 查看  方法字节码指令顺序

通过字节码指令顺序(invokespecial super → putfield 字段初始化 → 构造器体代码)可一锤定音。

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