2015-03-12 36 views
12

Consideriamo queste due classi:Inizializzazione ordine dei campi finali

public abstract class Bar { 
    protected Bar() { 
     System.out.println(getValue()); 
    } 

    protected abstract int getValue(); 
} 

public class Foo extends Bar { 
    private final int i = 20; 

    public Foo() { 
    } 

    @Override 
    protected int getValue() { 
     return i; 
    } 

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

Se eseguo Foo, l'uscita è 20.

Se faccio il campo non finale, o se inizializzare nel Foo constructor, l'output è 0.

La mia domanda è: qual è l'ordine di inizializzazione in caso di campi finali e dove si trova questo comportamento descritto nella JLS?

Mi aspettavo di trovare qualche regola eccezionale sui campi finali here, ma a meno che non mi manchi qualcosa, non c'è.

Nota che so che non dovrei mai chiamare un metodo overridable da un costruttore. Non è questo il punto della domanda.

risposta

18

La variabile membro final int i è una variabile costante: 4.12.4. final Variables

Una variabile di tipo primitivo o tipo String, che è final e inizializzato con una fase di compilazione espressione costante (§15.28), è chiamato variabile costante.

Ciò ha conseguenze per l'ordine in cui le cose sono inizializzate, come descritto in 12.4.2. Detailed Initialization Procedure.

+0

Dovevo rimuovere il mio commento su 'final int' essere una costante di tempo * compilazione * (non aveva * prove giustificative * ..Stavo cercando questa risposta .. Grazie). +1: P .. – TheLostMind

+0

Buon punto. Ho perso questo riferimento "variabile costante" (che ossimoro!). Ma non riesco ancora a comprendere appieno le modifiche apportate alla procedura di inizializzazione dell'oggetto. Quando viene assegnata questa variabile costante? La procedura dettagliata di inizializzazione parla dell'inizializzazione della classe e parla dell'inizializzazione delle "variabili finali della classe", ma non sull'inizializzazione delle "variabili di istanza finali" (a meno che non manchi di nuovo qualcosa). –

+0

OK. L'ho trovato. http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.2 dice: "Alcune espressioni hanno un valore che può essere determinato al momento della compilazione, che sono espressioni costanti (§15.28). ". E http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.28 dice che "Nomi semplici (§6.5.6.1) che si riferiscono a variabili costanti (§4.12. 4) "sono espressioni costanti. Quindi 'i' in' getValue() 'è un nome semplice che si riferisce a una variabile costante, e il suo valore è quindi noto in fase di compilazione. –

4

Camminare attraverso il modo in cui questo appare in un modo "byte-code-ish".

Si dovrebbe già essere consapevoli del fatto che la prima istruzione effettiva in un costruttore deve essere una chiamata a super (sia con argomenti o senza).

Questa istruzione super restituisce quando il costruttore genitore è terminato e il super "oggetto" è completamente costruito. Così, quando si costruisce Foo si verifica quanto segue (in ordine):

// constant fields are initialized by this point 
Object.construction // constructor call of Object, done by Bar 
Bar.construction // aka: Foo.super() 
callinterface getValue() // from Bar constructor 
// this call is delegated to Foo, since that's the actual type responsible 
// and i is returned to be printed 
Foo.construction 

se si dovesse inizializzare nel costruttore, che sarebbe successo "ora", dopo getValue() erano già stati chiamati.

+0

Lo so, ma questo non spiega perché ho il valore 20 quando il campo è definitivo e 0 quando non lo è. –

+0

JB hmm ... beh, non voglio rubare la buona risposta di Jesper – Vogel612

+1

Quando ho letto le specifiche JVM, non richiedevo alcun codice che potesse essere raggiunto prima che la chiamata del costruttore genitore potesse fare qualsiasi cosa con l'oggetto in costruzione * eccetto per la memorizzazione dei valori nei campi della classe derivata *. I costrutti di gestione delle eccezioni che potevano aggirare un costruttore genitore erano proibiti, anche se non ricordo quali fossero le regole esatte utilizzate (ad esempio se qualsiasi codice che era raggiungibile tramite un'eccezione che poteva essere lanciata prima del completamento del costruttore genitore sarebbe considerato eseguito senza il costruttore genitore è stato chiamato). – supercat

0

Ecco una versione ancora più subdola, solo per divertimento.

public abstract class Bar { 
    protected Bar() { 
     System.out.println(getValue()); 
    } 

    protected abstract Object getValue(); 
} 

public class Foo extends Bar { 
    private final String i = "Hello"; 

    public Foo() { 
    } 

    @Override 
    protected Object getValue() { 
     return i; 
    } 

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

Risultato: stampa Hello.

Ora cambiare questa linea:

private final String i = "Hello"; 

a:

private final Object i = "Hello"; 

Risultato: Stampe null.

Questo perché String (e tipi primitivi) sono gestiti appositamente come descritto in JLS 4.12.4 che ho citato nella mia altra risposta.