2013-07-15 11 views
8

Devo aver passato oltre un'ora cercando di capire il motivo di un comportamento inaspettato. Ho finito per rendermi conto che un campo non veniva impostato come mi sarei aspettato. Prima di scrollarmi di dosso e andare avanti, mi piacerebbe capire perché funziona così.Perché i campi non sono inizializzati su valori non predefiniti quando un metodo viene eseguito da super()?

Nell'esecuzione dell'esempio seguente, mi aspetto che l'output sia vero, ma è falso. Altri test mostrano che ottengo sempre tutto ciò che è il valore predefinito del tipo.

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(); 
    } 
} 

uscita:

bool=false 
+1

SÌ sono inizializzati sul valore predefinito, il valore di default booleano è 'false' – nachokk

risposta

7
boolean bool = true; 

public ClassTwo() { 
    super(); 
} 

è identico a

boolean bool; 

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

Il compilatore sposta automaticamente i campi inizializzazioni all'interno del costruttore (subito dopo la chiamata super-costruttore, implicitamente o esplicitamente).

Poiché un valore predefinito campo booleano è false, quando super() si chiama (e quindi ClassOne() e fireMethod()), bool non è stato impostato true ancora.


curiosità: il seguente costruttore

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

sarà intesa come

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

da JVM, e l'uscita sarà quindi

bool=false 
bool=true 
5

La costruzione della superclasse viene chiamato prima del costruttore della sottoclasse. E in Java, prima dell'esecuzione di un costruttore, tutti i membri dell'istanza hanno il loro valore predefinito (false, 0, null). Quindi, quando viene chiamato super(), bool è ancora falso (valore predefinito per booleani).

Più in generale, chiamare un metodo sovrascrivibile da un costruttore (in ClassOne) è una cattiva idea per il motivo che hai appena scoperto: potresti finire a lavorare su un oggetto che non è stato ancora completamente inizializzato.

1

Gli inizializzatori di istanza vengono eseguiti dopo che super() viene chiamato implicitamente o esplicitamente.

Dal linguaggio Java Specification, section 12.5: "Creation of new class instances:.

"3. Questo costruttore non inizia con un costruttore invocazione esplicita di un altro costruttore della stessa classe (usando questo) Se questo costruttore è per una classe diversa da Object, quindi questo costruttore inizierà con un esplicito o invocazione implicita di un costruttore di superclasse (utilizzando super). Valuta gli argomenti ed elabora in modo ricorsivo la chiamata del costruttore superclasse utilizzando gli stessi cinque passaggi. , quindi questo la procedura si conclude bruscamente per lo stesso motivo. Altrimenti, continuare al punto 4.

"4. Eseguire inizializzatori istanza e istanza inizializzatori variabili per questa classe, assegnando i valori di istanza initializers variabili ai corrispondenti variabili di istanza, nell'ordine da sinistra a destra in cui appaiono testuali in codice il codice sorgente per la classe.Se l'esecuzione di uno di questi inizializzatori risulta in un'eccezione, non vengono elaborati ulteriori inizializzatori e questa procedura completa improvvisamente con la stessa eccezione. Altrimenti, continuare con il passaggio 5 ".

0

super() è in realtà superflue (gioco di parole non destinato), in questo caso, perché è chiamato implicitamente in ogni costruttore. Quindi, ciò significa che viene chiamato per primo il costruttore di ClassOne. Quindi, prima dell'esecuzione di un costruttore, i membri dell'istanza hanno i loro valori predefiniti (quindi bool è false). È solo dopo che i costruttori vengono eseguiti, che i campi sono inizializzati.

Così il vostro costruttore diventa efficace:

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

    bool = true; //initialize members; 
} 

Ma ClassOne chiama il metodo override che stampa il valore di bool, che è false in quel punto.

In generale, è una cattiva pratica chiamare metodi sovrascrivibili da un costruttore (come si sta facendo in ClassOne) perché ora si sta lavorando con un oggetto che non è completamente inizializzato.

Da Effective Java (2a edizione):

Ci sono alcune più restrizioni che una classe deve obbedire per consentire l'ereditarietà. I costruttori non devono invocare metodi sovrascrivibili, direttamente o indirettamente. Se si viola questa regola, si verificherà un errore del programma. Il costruttore della superclasse viene eseguito prima del costruttore della sottoclasse, quindi il metodo di override della sottoclasse verrà richiamato prima dell'esecuzione del costruttore della sottoclasse. Se il metodo di sostituzione dipende da qualsiasi inizializzazione eseguita dal costruttore della sottoclasse, il metodo non si comporterà come previsto.

0

La finale la risposta sarà: non usare una meth overridable od in un costruttore.

In ogni costruttore:

  • chiamata il super costruttore (implicito o prima nel costruttore)
  • fanno i Inizializzazioni campo della classe corrente (type field = value;)
  • fanno il resto del costruttore

Questo rende la vita interessante

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); 
    } 
} 

Questo si traduce in

B.init a=0, b=0 
B.f a=13, b=15 
  1. come durante la chiamata di campi B.init B sono quelli azzerate.
  2. sarà impostato a a 7 (per niente), e b a 15.
  3. Poi nel costruttore di B a viene inizializzato.

Il mio IDE contrassegna già un metodo sovrascrivibile in un costruttore come cattivo stile.

Problemi correlati