Haben Java Final-Variablen Standardwerte?

81

Ich habe ein Programm wie dieses:

class Test {

    final int x;

    {
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }

}

Wenn ich versuche, es auszuführen, erhalte ich einen Compilerfehler wie variable x might not have been initializedfolgt : Basierend auf Java-Standardwerten sollte ich die folgende Ausgabe richtig machen?

"Here x is 0".

Werden endgültige Variablen Standardwerte haben?

Wenn ich meinen Code so ändere,

class Test {

    final int x;

    {
        printX();
        x = 7;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }

}

Ich erhalte folgende Ausgabe:

Here x is 0                                                                                      
Here x is 7                                                                                     
const called

Kann jemand bitte dieses Verhalten erklären ..

user3766874
quelle

Antworten:

62

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html , Kapitel "Initialisieren von Instanzmitgliedern":

Der Java-Compiler kopiert Initialisierungsblöcke in jeden Konstruktor.

Das heißt:

{
    printX();
}

Test() {
    System.out.println("const called");
}

verhält sich genau wie:

Test() {
    printX();
    System.out.println("const called");
}

Wie Sie sehen können, wurde nach dem Erstellen einer Instanz das endgültige Feld nicht eindeutig zugewiesen , während (von http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html) # jls-8.3.1.2 ):

Eine leere endgültige Instanzvariable muss definitiv am Ende jedes Konstruktors der Klasse zugewiesen werden, in der sie deklariert ist. Andernfalls tritt ein Fehler bei der Kompilierung auf.

Während es in den Dokumenten nicht explizit angegeben zu sein scheint (zumindest konnte ich es nicht finden), muss ein letztes Feld vorübergehend seinen Standardwert vor dem Ende des Konstruktors annehmen, damit es einen vorhersehbaren Wert hat, wenn Sie Lesen Sie es vor seiner Zuordnung.

Standardwerte: http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5

Bei Ihrem zweiten Snippet wird x bei der Instanzerstellung initialisiert, sodass sich der Compiler nicht beschwert:

Test() {
    printX();
    x = 7;
    printX();
    System.out.println("const called");
}

Beachten Sie auch, dass der folgende Ansatz nicht funktioniert. Die Verwendung des Standardwerts der endgültigen Variablen ist nur über eine Methode zulässig.

Test() {
    System.out.println("Here x is " + x); // Compile time error : variable 'x' might not be initialized
    x = 7;
    System.out.println("Here x is " + x);
    System.out.println("const called");
}
sp00m
quelle
1
Es kann erwähnenswert sein, wohin der implizite (oder explizite) Aufruf von super () in einem Ihrer Beispiele geht.
Patrick
2
Dies beantwortet nicht, warum das Nichtinitialisieren des letzten Felds einen Kompilierungsfehler verursacht.
halb
@ sp00m Gute Referenz - das werde ich auf die Bank legen.
Bohemian
2
@justhalf der Antwort fehlt ein entscheidender Punkt. Sie können auf ein Finale im Standardzustand (über eine Methode) zugreifen, aber der Compiler beschwert sich, wenn Sie es nicht vor dem Ende des Konstruktionsprozesses initialisieren. Deshalb funktioniert der zweite Versuch (er initialisiert tatsächlich x), aber nicht der erste. Der Compiler wird sich auch beschweren, wenn Sie versuchen, direkt auf ein leeres Finale zuzugreifen.
Luca
28

JLS ist zu sagen , dass Sie müssen den Standardwert leer letzte Instanz - Variable in Konstruktor zuweisen (oder in Initialisierungsbaustein das ist ziemlich gleich). Deshalb erhalten Sie im ersten Fall den Fehler. Es heißt jedoch nicht, dass Sie zuvor im Konstruktor nicht darauf zugreifen können. Sieht ein bisschen komisch aus, aber Sie können vor der Zuweisung darauf zugreifen und den Standardwert für int - 0 sehen.

UPD. Wie von @ I4mpi erwähnt, definiert JLS die Regel, dass jeder Wert vor jedem Zugriff definitiv zugewiesen werden sollte:

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.

Es gibt jedoch auch eine interessante Regel in Bezug auf Konstruktoren und Felder:

If C has at least one instance initializer or instance variable initializer then V is [un]assigned after an explicit or implicit superclass constructor invocation if V is [un]assigned after the rightmost instance initializer or instance variable initializer of C.

Im zweiten Fall wird der Wert xalso definitiv am Anfang des Konstruktors zugewiesen , da er die Zuweisung am Ende enthält.

udalmik
quelle
Tatsächlich heißt es , dass Sie vor der Zuweisung nicht darauf zugreifen können : "Jede lokale Variable (§14.4) und jedes leere Endfeld (§4.12.4, §8.3.1.2) müssen einen definitiv zugewiesenen Wert haben, wenn ein Zugriff auf ihren Wert erfolgt "
l4mpi
1
es sollte "definitiv zugewiesen" sein, jedoch hat diese Regel ein seltsames Verhalten in Bezug auf den Konstruktor. Ich habe die Antwort aktualisiert
udalmik
Wenn es eine Codemethode gibt, die abhängig von einigen komplexen Bedingungen ein finalFeld lesen kann oder nicht , und wenn dieser Code sowohl vor als auch nach dem Schreiben des Feldes ausgeführt werden kann, hätte ein Compiler im allgemeinen Fall keine Möglichkeit dazu zu wissen, ob es jemals das Feld lesen würde, bevor es geschrieben wurde.
Supercat
7

Wenn Sie nicht initialisieren x, wird ein Fehler beim Kompilieren angezeigt, da dieser xnie initialisiert wird.

xAls endgültig deklarieren bedeutet, dass es nur im Konstruktor oder im Initialisiererblock initialisiert werden kann (da dieser Block vom Compiler in jeden Konstruktor kopiert wird).

Der Grund, warum Sie 0vor der Initialisierung der Variablen ausgedruckt werden, ist auf das im Handbuch definierte Verhalten zurückzuführen (siehe Abschnitt "Standardwerte"):

Standardwerte

Es ist nicht immer erforderlich, einen Wert zuzuweisen, wenn ein Feld deklariert wird. Felder, die deklariert, aber nicht initialisiert sind, werden vom Compiler auf einen angemessenen Standardwert gesetzt. Im Allgemeinen ist dieser Standardwert je nach Datentyp Null oder Null. Das Verlassen auf solche Standardwerte wird jedoch allgemein als schlechter Programmierstil angesehen.

Die folgende Tabelle fasst die Standardwerte für die oben genannten Datentypen zusammen.

Data Type   Default Value (for fields)
--------------------------------------
byte        0
short       0
int         0
long        0L
float       0.0f
double      0.0d
char        '\u0000'
String (or any object)      null
boolean     false
Nir Alfasi
quelle
4

Der erste Fehler ist, dass der Compiler sich beschwert, dass Sie ein letztes Feld haben, aber keinen Code, um es zu initialisieren - einfach genug.

Im zweiten Beispiel haben Sie Code, um ihm einen Wert zuzuweisen, aber die Reihenfolge der Ausführung bedeutet, dass Sie das Feld sowohl vor als auch nach dem Zuweisen referenzieren.

Der vorab zugewiesene Wert eines Feldes ist der Standardwert.

Böhmisch
quelle
2

Alle nicht endgültigen Felder einer Klasse werden auf einen Standardwert initialisiert ( 0für nummerische Datentypen, falsefür boolesche und nullfür Referenztypen, manchmal auch als komplexe Objekte bezeichnet). Diese Felder werden initialisiert, bevor ein Konstruktor (oder ein Instanzinitialisierungsblock) ausgeführt wird, unabhängig davon, ob die Felder vor oder nach dem Konstruktor deklariert wurden.

Endgültige Felder einer Klasse haben keinen Standardwert und müssen nur einmal explizit initialisiert werden, bevor ein Klassenkonstruktor seinen Job beendet hat.

Lokale Variablen innerhalb eines Ausführungsblocks (z. B. einer Methode) haben keinen Standardwert. Diese Felder müssen vor ihrer ersten Verwendung explizit initialisiert werden, und es spielt keine Rolle, ob die lokale Variable als endgültig markiert ist oder nicht.

Martin Andersson
quelle
1

Lassen Sie es mich in den einfachsten Worten sagen, die ich kann.

finalVariablen müssen initialisiert werden, dies ist in der Sprachspezifikation vorgeschrieben. Bitte beachten Sie jedoch, dass eine Initialisierung zum Zeitpunkt der Deklaration nicht erforderlich ist.

Dies muss initialisiert werden, bevor das Objekt initialisiert wird.

Wir können Initialisierungsblöcke verwenden, um die endgültigen Variablen zu initialisieren. Es gibt zwei Arten von Initialisierungsblöcken: staticundnon-static

Der von Ihnen verwendete Block ist ein nicht statischer Initialisierungsblock. Wenn Sie also ein Objekt erstellen, ruft Runtime den Konstruktor auf, der wiederum den Konstruktor der übergeordneten Klasse aufruft.

Danach werden alle Initialisierer aufgerufen (in Ihrem Fall der nicht statische Initialisierer).

In Ihrer Frage, Fall 1 : Auch nach Abschluss des Initialisierungsblocks bleibt die endgültige Variable nicht initialisiert, was ein Fehler-Compiler erkennt.

In Fall 2 : Der Initialisierer initialisiert die endgültige Variable, daher weiß der Compiler, dass das Finale bereits initialisiert ist, bevor das Objekt initialisiert wird. Daher wird es sich nicht beschweren.

Nun ist die Frage, warum xnimmt eine Null. Der Grund hierfür ist, dass der Compiler bereits weiß, dass kein Fehler vorliegt. Beim Aufrufen der init-Methode werden alle Finals auf die Standardeinstellungen initialisiert und ein Flag gesetzt, das sie bei der tatsächlichen Zuweisungsanweisung ähnlich wie ändern können x=7. Siehe den Init-Aufruf unten:

Geben Sie hier die Bildbeschreibung ein

Dharam
quelle
1

Soweit mir bekannt ist, initialisiert der Compiler Klassenvariablen immer mit Standardwerten (sogar Endvariablen). Wenn Sie beispielsweise ein int für sich selbst initialisieren, wird das int auf den Standardwert 0 gesetzt. Siehe unten:

class Test {
    final int x;

    {
        printX();
        x = this.x;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }
}

Das Obige würde Folgendes drucken:

Here x is 0
Here x is 0
const called
Michael D.
quelle
1
Die endgültige Variable x ist im OP-Code nicht statisch.
JamesB
Ich könnte genauso gut den Code des OP ändern, um ihn auf this.x zu initialisieren, und das Gleiche würde passieren. Es spielt keine Rolle, ob es statisch ist oder nicht.
Michael D.
Ich würde vorschlagen, dass Sie den statischen Inhalt hier entfernen, da Sie die Frage des OP anscheinend nicht gelesen haben.
JamesB
Hilft es, wenn ich dann aus dem OP-Code eine Baseline mache? Wie gesagt, es spielt keine Rolle, ob die Variable statisch ist oder nicht. Mein Punkt ist, dass das Initialisieren einer Variablen für sich selbst und das Abrufen des Standardwerts impliziert, dass die Variable implizit initialisiert wird, bevor sie explizit initialisiert wird.
Michael D.
1
es wird nicht kompiliert, weil Sie versuchen, (direkt) auf eine endgültige Variable zuzugreifen, bevor sie in Zeile 6 initialisiert wird.
Luca
1

Wenn ich versuche, es auszuführen, erhalte ich einen Compilerfehler als: Variable x wurde möglicherweise nicht basierend auf Java-Standardwerten initialisiert. Ich sollte die folgende Ausgabe richtig machen.

"Hier ist x 0".

Nein. Diese Ausgabe wird nicht angezeigt, da in erster Linie ein Fehler bei der Kompilierung auftritt. Endgültige Variablen erhalten zwar einen Standardwert, aber für die Java-Sprachspezifikation (JLS) müssen Sie sie bis zum Ende des Konstruktors initialisieren (LE: Ich füge hier Initialisierungsblöcke ein). Andernfalls wird ein Fehler bei der Kompilierung angezeigt verhindert, dass Ihr Code kompiliert und ausgeführt wird.

In Ihrem zweiten Beispiel wird die Anforderung berücksichtigt. Deshalb wird (1) Ihr Code kompiliert und (2) Sie erhalten das erwartete Verhalten.

Versuchen Sie in Zukunft, sich mit dem JLS vertraut zu machen. Es gibt keine bessere Informationsquelle über die Java-Sprache.

asynchron
quelle