tl; dr
Für Felder , int b = b + 1ist illegal , weil bein illegaler Vorwärtsverweis auf ist b. Sie können dies tatsächlich durch Schreiben beheben int b = this.b + 1, das ohne Beschwerden kompiliert wird.
Für lokale Variablen , int d = d + 1ist illegal , weil dnicht vor der Verwendung initialisiert. Dies ist nicht der Fall bei Feldern, die immer standardmäßig initialisiert werden.
Sie können den Unterschied erkennen, indem Sie versuchen, zu kompilieren
int x = (x = 1) + x;
als Felddeklaration und als lokale Variablendeklaration. Ersteres wird scheitern, letzteres wird jedoch aufgrund der unterschiedlichen Semantik erfolgreich sein.
Einführung
Zunächst einmal sind die Regeln für Feld- und lokale Variableninitialisierer sehr unterschiedlich. Diese Antwort wird also die Regeln in zwei Teilen behandeln.
Wir werden dieses Testprogramm durchgehend verwenden:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
Die Deklaration von bist ungültig und schlägt mit einem illegal forward referenceFehler fehl .
Die Deklaration von dist ungültig und schlägt mit einem variable d might not have been initializedFehler fehl .
Die Tatsache, dass diese Fehler unterschiedlich sind, sollte darauf hinweisen, dass auch die Gründe für die Fehler unterschiedlich sind.
Felder
Feldinitialisierer in Java unterliegen JLS §8.3.2 , Initialisierung von Feldern.
Der Umfang eines Feldes ist in JLS §6.3 , Umfang einer Erklärung definiert.
Relevante Regeln sind:
- Der Umfang einer Deklaration eines Mitglieds
m, das in einem Klassentyp C deklariert oder von diesem geerbt wurde (§8.1.6), umfasst den gesamten Hauptteil von C, einschließlich aller verschachtelten Typdeklarationen.
- Initialisierungsausdrücke für Instanzvariablen können den einfachen Namen jeder statischen Variablen verwenden, die in der Klasse deklariert oder von dieser geerbt wurde, auch einer, deren Deklaration später in Textform erfolgt.
- Die Verwendung von Instanzvariablen, deren Deklarationen nach der Verwendung in Textform angezeigt werden, ist manchmal eingeschränkt, obwohl diese Instanzvariablen im Gültigkeitsbereich liegen. In §8.3.2.3 finden Sie die genauen Regeln für die Vorwärtsreferenz auf Instanzvariablen.
§8.3.2.3 sagt:
Die Deklaration eines Mitglieds muss in Textform angezeigt werden, bevor sie nur verwendet wird, wenn das Mitglied ein Instanzfeld (bzw. ein statisches Feld) einer Klasse oder Schnittstelle C ist und alle folgenden Bedingungen erfüllt sind:
- Die Verwendung erfolgt in einem instanziellen (bzw. statischen) Variableninitialisierer von C oder in einem instanziellen (bzw. statischen) Initialisierer von C.
- Die Verwendung befindet sich nicht auf der linken Seite einer Aufgabe.
- Die Verwendung erfolgt über einen einfachen Namen.
- C ist die innerste Klasse oder Schnittstelle, die die Verwendung einschließt.
Sie können tatsächlich auf Felder verweisen, bevor sie deklariert wurden, außer in bestimmten Fällen. Diese Einschränkungen sollen Code wie verhindern
int j = i;
int i = j;
vom Kompilieren. In der Java-Spezifikation heißt es: "Die oben genannten Einschränkungen dienen dazu, zum Zeitpunkt der Kompilierung zirkuläre oder anderweitig fehlerhafte Initialisierungen abzufangen."
Worauf laufen diese Regeln eigentlich hinaus?
Kurz gesagt, die Regeln besagen grundsätzlich, dass Sie ein Feld vor einer Referenz auf dieses Feld deklarieren müssen , wenn (a) sich die Referenz in einem Initialisierer befindet, (b) die Referenz nicht zugewiesen ist, (c) die Referenz a ist einfacher Name (keine Qualifikationsmerkmale wiethis. ) und (d) es wird nicht innerhalb einer inneren Klasse zugegriffen. Eine Vorwärtsreferenz, die alle vier Bedingungen erfüllt, ist unzulässig, aber eine Vorwärtsreferenz, die bei mindestens einer Bedingung fehlschlägt, ist in Ordnung.
int a = a = 1;Kompiliert, weil es gegen (b) verstößt: Die Referenz a wird zugewiesen, daher ist es legal, avor der avollständigen Erklärung darauf zu verweisen .
int b = this.b + 1Kompiliert auch, weil es gegen (c) verstößt: Die Referenz this.bist kein einfacher Name (mit dem sie qualifiziert ist this.). Dieses seltsame Konstrukt ist immer noch perfekt definiert, weilthis.b es den Wert Null hat.
Grundsätzlich verhindern die Einschränkungen für Feldreferenzen in Initialisierern, dass int a = a + 1sie erfolgreich kompiliert werden.
Beachten Sie, dass die Felddeklaration int b = (b = 1) + bwird nicht kompilieren, weil die letzte bnoch eine illegale vorwärts Referenz.
Lokale Variablen
Deklarationen für lokale Variablen unterliegen JLS §14.4 , Deklarationen für lokale Variablen.
Der Geltungsbereich einer lokalen Variablen ist in JLS §6.3 , Geltungsbereich einer Deklaration definiert:
- Der Umfang einer lokalen Variablendeklaration in einem Block (§14.4) ist der Rest des Blocks, in dem die Deklaration angezeigt wird, beginnend mit einem eigenen Initialisierer und einschließlich aller weiteren Deklaratoren rechts in der lokalen Variablendeklarationsanweisung.
Beachten Sie, dass Initialisierer im Bereich der deklarierten Variablen liegen. Warum also nicht int d = d + 1;kompilieren?
Der Grund liegt in Javas Regel zur endgültigen Zuweisung ( JLS §16 ). Die eindeutige Zuweisung besagt grundsätzlich, dass jeder Zugriff auf eine lokale Variable eine vorhergehende Zuweisung zu dieser Variablen haben muss, und der Java-Compiler überprüft Schleifen und Verzweigungen, um sicherzustellen, dass die Zuweisung immer vor jeder Verwendung erfolgt (aus diesem Grund ist der gesamten Zuweisung ein ganzer Spezifikationsabschnitt zugeordnet dazu). Die Grundregel lautet:
- Für jeden Zugriff auf eine lokale Variable oder ein leeres Endfeld muss vor dem Zugriff definitiv zugewiesen werden
x, da xsonst ein Fehler bei der Kompilierung auftritt.
Im int d = d + 1; wird der Zugriff auf ddie lokale Variable fine aufgelöst. Da djedoch vor dem dZugriff noch keine Zuweisung erfolgt ist, gibt der Compiler einen Fehler aus. In int c = c = 1, c = 1passiert zuerst, der Abtretungsempfänger c, und dann cauf das Ergebnis dieser Zuordnung initialisiert wird (das ist 1).
Beachten Sie, dass aufgrund bestimmter Zuordnungsregeln, die lokale Variablendeklaration int d = (d = 1) + d; wird erfolgreich kompiliert ( im Gegensatz zu der Felddeklarationint b = (b = 1) + b ), da auf djeden Fall von der Zeit zugewiesen wird die endgültige derreicht ist.
staticin der Variablen class-scope hinzufügenstatic int x = x + 1;, erhalten Sie denselben Fehler? Denn in C # macht es einen Unterschied, ob es statisch oder nicht statisch ist.