2015-03-12 8 views
12

diese beiden Klassen vor:Initialisierungsreihenfolge der endgültigen Felder

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

Wenn ich Foo ausführen, wird der Ausgang 20

ist Wenn ich das Feld nicht endgültig zu machen, oder wenn ich initialisieren es in der Foo Konstruktor, der Ausgang ist 0.

Meine Frage ist: Was ist die Reihenfolge der Initialisierung im Falle der endgültigen Felder und wo ist dieses Verhalten in der JLS beschrieben?

Ich erwartete, einige Ausnahme Regel über endgültige Felder here zu finden, aber wenn ich etwas nicht vermisse, gibt es nicht.

Beachten Sie, dass ich weiß, ich sollte nie eine überschreibbare Methode von einem Konstruktor aufrufen. Das ist nicht der Punkt der Frage.

Antwort

18

Ihre final int i Membervariable ist eine konstante Größe: 4.12.4. final Variables

Eine Variable vom Urtyp oder Typ String, dh final und mit einem Kompilierung-konstanten Ausdruck initialisiert (§15.28) genannt wird, ein Konstante Variable.

Dies hat Folgen für die Reihenfolge, in der Dinge initialisiert werden, wie in 12.4.2. Detailed Initialization Procedure beschrieben.

+0

Hatte meinen Kommentar über 'final int' zu entfernen, da es eine * Kompilierzeitkonstante * war (hatte keine * unterstützenden Beweise * ..War auf der Suche nach dieser Antwort .. Danke). +1: P .. – TheLostMind

+0

Guter Punkt. Ich habe diese Referenz "konstante Variable" vermisst (was für ein Oxymoron!). Aber ich verstehe immer noch nicht, was sich bei der Objektinitialisierung ändert. Wann ist diese konstante Variable zugewiesen? Die detaillierte Initialisierungsprozedur spricht über die Klasseninitialisierung und spricht über die Initialisierung von "finalen Klassenvariablen", aber nicht über die Initialisierung von "letzten Instanzvariablen" (es sei denn, ich vermisse etwas wieder). –

+0

OK. Ich habe es gefunden. http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.2 sagt: "Einige Ausdrücke haben einen Wert, der zur Kompilierzeit bestimmt werden kann. Dies sind konstante Ausdrücke (§15.28). ". Und http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.28 sagt, dass "Einfache Namen (§6.5.6.1), die sich auf konstante Variablen beziehen (§4.12. 4) "sind konstante Ausdrücke. Also ist 'i' in' getValue() 'ein einfacher Name, der sich auf eine konstante Variable bezieht und dessen Wert somit zur Kompilierzeit bekannt ist. –

4

Gehen Sie durch, wie dies auf eine "Byte-Code-ish" Weise aussieht.

Sie sollten bereits wissen, dass die erste tatsächliche Anweisung in einem Konstruktor ein Aufruf an super (ob mit Argumenten oder ohne) sein muss.

Dieser Superbefehl kehrt zurück, wenn der Elternkonstruktor fertig ist und das Superobjekt vollständig aufgebaut ist. Also, wenn Sie Foo konstruieren passiert folgendes (in Reihenfolge):

// 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 

, wenn Sie es in den Konstruktor zu initialisieren waren, dass passieren würde, „jetzt“, nachdem getValue() bereits genannt worden war.

+0

Ich weiß das, aber das erklärt nicht, warum ich den Wert 20, wenn das Feld ist final und 0, wenn es nicht ist. –

+0

JB hmm ... naja, ich will Jespers gute Antwort nicht wirklich stehlen – Vogel612

+1

Wenn ich die JVM-Spezifikation gelesen habe, war es erforderlich, dass kein Code, der vor dem Aufruf des Elternkonstruktors erreicht werden konnte, irgendetwas mit dem Objekt, das sich im Aufbau befand, tat zum Speichern von Werten in abgeleiteten Klassenfeldern *. Ausnahmebehandlungskonstrukte, die einen Elternkonstruktor umgehen könnten, waren verboten, obwohl ich mich nicht erinnere, welche genauen Regeln verwendet wurden (z. B. wenn Code, der über eine Ausnahme erreicht wurde, die vor dem Erledigen des Elternkonstruktors ausgelöst wurde, als ohne ausgeführt wurde der Elternkonstruktor wurde aufgerufen). – supercat

0

Hier ist eine noch mehr hinterhältige Version, nur zum Spaß.

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

Ergebnis: Drucke Hello.

nun diese Zeile:

private final String i = "Hello"; 

zu:

private final Object i = "Hello"; 

Ergebnis: Prints null.

Dies ist, weil String (und primitive Typen) behandelt werden speziell wie in JLS 4.12.4 beschrieben, die ich in meiner anderen Antwort erwähnt.

Verwandte Themen