簡體   English   中英

當從super()運行方法時,為什么字段沒有初始化為非默認值?

[英]Why aren't fields initialized to non-default values when a method is run from super()?

我一定花了一個多小時試圖找出出現意外行為的原因。 我最終意識到沒有像我期望的那樣設置場地。 在聳聳肩並繼續前進之前,我想明白為什么會這樣。

在運行下面的示例時,我希望輸出為true,但它是錯誤的。 其他測試表明,我總是得到任何類型的默認值。

public class ClassOne {

    public ClassOne(){
        fireMethod();
    }

    protected void fireMethod(){
    }

}

public class ClassTwo extends ClassOne {

    boolean bool = true;

    public ClassTwo() {
        super();
    }

    @Override
    protected void fireMethod(){
        System.out.println("bool="+bool);
    }

    public static void main(String[] args) {
        new ClassTwo();
    }
}

輸出:

bool=false
boolean bool = true;

public ClassTwo() {
    super();
}

是完全相同的

boolean bool;

public ClassTwo() {
    super();
    bool = true;
}

編譯器自動在構造函數中移動字段初始化(在超級構造函數調用之后,隱式或顯式)。

由於布爾字段默認值為false ,因此調用super() (因此ClassOne()fireMethod() )時, bool尚未設置為true


有趣的事實:以下構造函數

public ClassTwo() {
    super();
    fireMethod();
}

將被理解為

public ClassTwo() {
    super();
    bool = true;
    fireMethod();
}

由JVM,因此輸出將是

bool=false
bool=true

在子類構造函數之前調用超類構造函數。 在Java中,在運行構造函數之前,所有實例成員都有其默認值(false,0,null)。 所以當調用super()時, bool仍然是假的(布爾值的默認值)。

更一般地說,從構造函數(在ClassOne中)調用可覆蓋的方法是一個壞主意,因為您剛剛發現:您最終可能還在處理尚未完全初始化的對象。

在隱式或顯式調用super()之后執行實例初始值設定項。

從Java語言規范, 第12.5節:“創建新的類實例

“3.此構造函數不是以同一個類中的另一個構造函數的顯式構造函數調用開始的(使用此方法)。如果此構造函數用於Object以外的類,則此構造函數將以顯式或隱式調用超類開始構造函數(使用super)。使用這五個相同的步驟評估參數並遞歸處理超類構造函數調用。如果構造函數調用突然完成,則此過程會因同樣的原因突然完成。否則,繼續執行步驟4。

“4.為此類執行實例初始值設定項和實例變量初始值設定項,將實例變量初始值設定項的值分配給相應的實例變量,按從左到右的順序,它們以文本方式顯示在類的源代碼中。執行任何這些初始值設定項都會導致異常,然后不再處理其他初始值設定項,並且此過程會突然完成同樣的異常。否則,繼續執行步驟5。

在這種情況下, super()實際上是多余的(雙關語),因為它在每個構造函數中被隱式調用。 所以這意味着首先調用ClassOne的構造函數。 因此,在運行構造函數之前,實例成員具有其默認值(因此boolfalse )。 只有構造函數運行之后,才會初始化字段。

所以你的構造函數有效地成為:

public ClassTwo() {
    super(); //call constructor of super class

    bool = true; //initialize members;
}

ClassOne調用可ClassOne的方法,該方法打印出bool的值,此時該值為false

通常,從構造函數中調用可覆蓋的方法(就像在ClassOne )是不好的做法,因為您現在正在處理未完全初始化的對象。

來自Effective Java(第2版):

為了允許繼承,類必須遵守一些限制。 構造函數不得直接或間接調用可覆蓋的方法。 如果違反此規則,將導致程序失敗。 超類構造函數在子類構造函數之前運行,因此在子類構造函數運行之前將調用子類中的重寫方法。 如果重寫方法依賴於子類構造函數執行的任何初始化,則該方法將不會按預期運行。

最后的答案是:不要在構造函數中使用可覆蓋的方法。

在每個構造函數中:

  • 調用超級構造函數(隱式或構造函數中的第一個)
  • 進行當前類的字段初始化( type field = value;
  • 做其余的構造函數

這讓生活變得有趣

public class A {

    public A() {
        init();
    }

    protected void init() {
    }
}

public class B extends A {
    int a = 13;
    int b;

    @Override
    protected void init() {
        System.out.println("B.init a=" + a + ", b=" + b);
        a = 7;
        b = 15;
    }

    public static void main(String[] args) {
        new B().f();
    }

    public void f() {
        System.out.println("B.f a=" + a + ", b=" + b);
    }
}

這導致了

B.init a=0, b=0
B.f a=13, b=15
  1. 在B.init B的調用期間,字段是歸零的。
  2. 它將a設置為7(無效), b為15。
  3. 然后在B的構造a被初始化。

我的IDE已經將構造函數中的可覆蓋方法調用為壞樣式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM