2010-07-20 140 views
3

赫雷什一些示例代碼,問題的實例變量初始化

class Base 
{ 
    private int val; 

    Base() { 
    val = lookup(); 
    } 

    public int lookup() { 
    //Perform some lookup 
    // int num = someLookup(); 
    return 5; 
    } 

    public int value() { 
    return val; 
    } 
} 

class Derived extends Base 
{ 
    private int num = 10; 

    public int lookup() { 
    return num; 
    } 
} 


class Test 
{ 
    public static void main(String args[]) { 

    Derived d = new Derived(); 
    System.out.println("d.value() returns " + d.value()); 

    } 
} 

輸出:d.value()返回0 //我預期10查找()被重寫,但不是0!有人可以澄清這一點?

Derived的實例變量的初始化在其查找方法執行時未發生。如何確保在調用其方法時初始化實例變量Derived

+0

'value()'僅在Base中定義,並返回僅在Base中定義的'val',在Base構造函數中由'5'初始化。 'lookup'是從Base構造函數調用的,這意味着重載不起作用,因爲Derived類尚未完全構建。您還期望從代碼中得到什麼? – ULysses 2010-07-20 09:36:11

+0

@ULysses,在發佈的代碼中重載?我認爲你的意思是壓倒性的。 – Zaki 2010-07-20 10:01:47

+0

@ULysses,爲什麼沒有壓倒一切的工作? – Zaki 2010-07-20 10:03:34

回答

8

一開始,該代碼不編譯由於缺乏someLookup方法:

如果我理解正確的話你的意圖,你應該改變value方法Base是。

無論如何,除此之外,我相信你的問題是你的期望是無效的,因爲構造函數是分層運行的方式。

超類的構造函數總是在子類之前運行,這包括子類變量的初始化方法(它們實際上是作爲構造函數的一部分運行的)。所以,當你創建的Derived你的情況下,會發生以下情況:

  1. Base構造函數首先被調用。
  2. lookup()被調用,它使用Derived中的實現。
  3. num返回,這是此時的默認值,因爲Derived的構造函數和初始值設定項尚未運行
  4. val設置爲0
  5. Derived初始化和構造都運行 - 呼叫lookup點上會返回10。

一般來說,由於這個原因,從構造函數中調用非最終方法是一個壞主意,許多靜態分析工具都會警告你。這與在構建過程中讓對象引用泄露相似,最終可能會導致類級別不變量實例失效(對於您的情況,Derived的num「始終」爲10,但在某些點可以看到它爲0)。

編輯:請注意,此特殊情況下,無需任何額外的代碼,你可以通過使num解決問題的常數:

class Derived extends Base 
{ 
    private static final int num = 10; 
    ... 

這實際上做你想做的,因爲靜態初始化在類被加載時運行(這必須在調用構造函數之前發生)。然而,這確實適用於:

a)該類的所有實例共享相同的num變量; b)num永不需要改變(如果這是真的,那麼(a)自動爲真)。

在給出的確切代碼中顯然是這種情況,但我希望您爲簡潔起見可能會省略其他功能。

我在這裏包括這個比較和興趣,不是因爲它是一般意義上的這個「問題」的解決方法(因爲它不是)。

+0

lookup()只是虛擬的,可以註釋掉它。 – Zaki 2010-07-20 09:43:38

+0

關於Java構造函數和模板方法模式的相關文章:http://novyden.blogspot.com/2011/08/java-anti-pattern-constructors-and.html – topchef 2011-08-08 08:04:30

4

你得到0返回的原因是構造函數Base在Derived中被賦值爲num之前被調用(並且在Derived中調用查找)。

通常,在派生實例字段被初始化之前調用基礎構造函數。

+0

這意味着派生類的方法可以運行,即使其實例變量尚未初始化? – Zaki 2010-07-20 10:08:53

+0

@Zaki,沒錯。正如Andrzej Doyle所說,這就是爲什麼從構造函數中調用非最終方法是一個壞主意。我同意這種行爲是違反直覺的,即使它具有一定的一致性(基類在派生類之前初始化)。 – andrewmu 2010-07-20 10:42:20

1

你重寫方法lookup()Derived類,所以當Base調用構造函數調用從Derived哪個機構return num的方法。在初始化Base時,Derivednum實例變量尚未初始化並且爲0.這就是爲什麼在Base中將val分配給0的原因。那麼

public int value() { 
return lookup(); 
} 
1

當構造函數調用此函數時,下面的代碼片段返回0(通過查看程序,您期望爲10)。簡單的原因是num尚未初始化,父類調用此方法。

public int lookup() { 
    return num; 
} 
2

在一個可以在子類中重寫的構造函數中調用方法通常是個壞主意。在您的例子會發生以下情況:

  • 派生構造函數被調用
    • 基類的構造被稱爲第一個動作
    • 基本構造函數調用查詢
  • 派生構造仍在繼續,initialies NUM到10

由於子類構造函數在基礎構造函數調用lookup時沒有完成,對象尚未完全初始化,查找返回num字段的默認值。

+0

+1,經驗教訓:這確實是不好的做法從構造函數中調用非final方法。 – Zaki 2010-07-20 10:46:22

2

讓我們慢慢來:

class Test 
{ 
    public static void main(String args[]) { 
    // 1 
    Derived d = new Derived(); 
    // 2 
    System.out.println("d.value() returns " + d.value());  
    } 
} 

第1步,調用(默認值)上構造派生,之前設置NUM = 10,它鏈條高達基地的構造函數,調用Derived的查找方法,但NUM尚未設置,因此val仍未初始化。

第2步,你調用d.value(),它屬於Base,並且val由於1而未被設置,因此你得到0而不是10。

2

已經有很多偉大的答案對爲什麼而構建的基類,你不能訪問子領域,但我想你問一個如何:這樣的事情一個有效的解決方案:

public abstract class Animal { 
    public Animal() { 
    System.println(whoAmI()); 
    } 
    public abstract String whoAmI(); 
} 

public Lion() extends Animal { 
    private String iAmA = "Lion"; 
    public Lion(){super();} 
    public String whoAmI() {return iAmA;} 
} 

實用的方法是在基類引入的init()方法從子類的構造函數中調用它,如:

public abstract class Animal { 
    private boolean isInitialized = false; 
    public Animal() {} 
    void init() { 
    isInitialized = true; 
    System.out.println(whoAmI()); 
    } 
    public abstract String whoAmI(); 
    public void someBaseClassMethod() { 
    if (!isInitialized) 
     throw new RuntimeException("Baseclass has not been initialized"); 
    // ... 
    } 
} 

public Lion() extends Animal { 
    private String iAmA = "Lion"; 
    public Lion() { 
    super(); 
    init(); 
    } 
    public String whoAmI() {return iAmA;} 
} 

唯一的問題是,你無法強制子類在基類上調用init()方法,並且基類可能未正確初始化。但是有了一個標誌和一些例外情況,我們可以在運行時提醒程序員他應該叫init() ...

+0

從子類構造函數調用init()時出現的一個問題是,這樣的方法不能寫入任何「final」字段。如果每個級別的構造函數都接受一個'object',它將它傳遞給超類的構造函數,或者將其封裝在一個給予超類構造函數的對象中,並且每個級別都有一個虛擬方法'getConstructorParam()'返回'object'被傳遞給它的構造函數(如果需要,從'super.getConstructorParam()''返回的類中提取),那麼子類應該能夠在基類構造函數完成之前完成它們初始化的一部分。 – supercat 2013-05-21 19:34:04

+0

我會質疑任何設計的最終領域是由一個子類初始化;) – 2013-05-22 10:24:53