作者提供了一個抽象對話框基類 BaseDlg,要求子類實現 createClientPanel() 方法。大多數代碼正常,但同事的 ChildDlg2 拋出 NullPointerException。對比發現,唯一差別在於實例變量 jTextFieldName 的初始化位置:
// 正常:ChildDlg1 - 在方法內初始化
public JPanel createClientPanel() {
jTextFieldName = new JTextField(); // 此處初始化
...
}
// 異常:ChildDlg2 - 在字段聲明處初始化
JTextField jTextFieldName = new JTextField(); // 字段初始化器
public JPanel createClientPanel() {
panel.add(jTextFieldName); // 此時為 null!
...
}
通俗認知認為「字段初始化發生在構造器之前」——這在單類情況下成立,但繼承鏈下並不準確。完整流程包含 5 步遞歸:
this(...) 調用本類其他構造器 → 遞歸執行 5 步,正常則跳至步驟 5this(...) 且非 Object 類 → 調用父類構造器 (super(...) 顯式或隱式) → 遞歸執行 5 步,正常則跳至步驟 5核心結論:父類構造器執行發生在子類字段初始化之前。

class Animal { Animal() { System.out.println("Animal"); } }
class Cat extends Animal { Cat() { System.out.println("Cat"); } }
class Store { Store() { System.out.println("Store"); } }
public class Petstore extends Store {
Cat cat = new Cat(); // 字段初始化器
Petstore() { System.out.println("Petstore"); }
public static void main(String[] args) { new Petstore(); }
}
輸出順序:Store → Animal → Cat → Petstore,證明父類構造器 → 子類字段初始化 → 子類構造器主體。
ChildDlg2 實例 → 調用 super(null, "Title")BaseDlg 構造器BaseDlg 構造器中調用 createClientPanel() (抽象方法)ChildDlg2.createClientPanel()jTextFieldName,但子類字段初始化尚未執行 (步驟 4 尚未到達)jTextFieldName 仍為默認值 null → 拋出 NullPointerExceptionChildDlg1 將初始化移入方法內,故在調用時已完成賦值,避開陷阱。
要求子類在實現方法時先初始化所需字段。但這違背「字段初始化宜在聲明處或構造器」的良好設計原則,且依賴開發者自覺,風險大。
將「佈局用戶界面」職責從構造器剝離,延遲到構造完成後調用:
public abstract class BaseDlg extends JDialog {
public BaseDlg(Frame frame, String title) {
super(frame, title, true);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(createHeadPanel(), BorderLayout.NORTH);
getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
// 移除此處對 createClientPanel() 的調用
}
/** 構造完成後必須調用此方法完成界面佈局 */
public void initGUI() {
getContentPane().add(createClientPanel(), BorderLayout.CENTER);
}
// ... 其他方法不變
}
使用方式:
ChildDlg dlg = new ChildDlg();
dlg.initGUI(); // 此時子類字段已全部初始化完畢
dlg.setVisible(true);
構造器的基本職責是正確初始化字段,讓對象進入就緒狀態,切勿賦予過多職責。
final 方法或 private 方法 (編譯器隱式 final)final 方法不可被子類覆寫,不會觸發多態調用導致字段未初始化遵循此原則,可從根源消除「構造器調用可覆寫方法」引發的初始化順序陷阱,編寫更健壯的 Java 代碼。