Im Methoden- oder Klassenbereich wird die folgende Zeile kompiliert (mit Warnung):
int x = x = 1;
Im Klassenbereich, in dem Variablen ihre Standardwerte erhalten , gibt der folgende Fehler "undefinierte Referenz" aus:
int x = x + 1;
Ist es nicht das erste Mal x = x = 1
, dass der Fehler "undefinierte Referenz" auftritt? Oder sollte die zweite Zeile int x = x + 1
kompiliert werden? Oder fehlt mir etwas?
java
compiler-construction
Marcin
quelle
quelle
static
in 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.static int x = x + 1
schlägt in Java fehl.int a = this.a + 1;
undint b = 1; int a = b + 1;
im Klassenbereich (beide sind in Java in Ordnung) schlagen fehl, wahrscheinlich aufgrund von §17.4.5.2 - "Ein Variableninitialisierer für ein Instanzfeld kann nicht auf die erstellte Instanz verweisen." Ich weiß nicht, ob es explizit irgendwo erlaubt ist, aber statisch hat keine solche Einschränkung. In Java sind die Regeln unterschiedlich undstatic int x = x + 1
int x = x + 1
Antworten:
tl; dr
Für Felder ,
int b = b + 1
ist illegal , weilb
ein illegaler Vorwärtsverweis auf istb
. Sie können dies tatsächlich durch Schreiben behebenint b = this.b + 1
, das ohne Beschwerden kompiliert wird.Für lokale Variablen ,
int d = d + 1
ist illegal , weild
nicht 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:
Die Deklaration von
b
ist ungültig und schlägt mit einemillegal forward reference
Fehler fehl .Die Deklaration von
d
ist ungültig und schlägt mit einemvariable d might not have been initialized
Fehler 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:
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.§8.3.2.3 sagt:
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
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 wie
this.
) 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 Referenza
wird zugewiesen, daher ist es legal,a
vor dera
vollständigen Erklärung darauf zu verweisen .int b = this.b + 1
Kompiliert auch, weil es gegen (c) verstößt: Die Referenzthis.b
ist kein einfacher Name (mit dem sie qualifiziert istthis.
). 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 + 1
sie erfolgreich kompiliert werden.Beachten Sie, dass die Felddeklaration
int b = (b = 1) + b
wird nicht kompilieren, weil die letzteb
noch 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:
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:
x
, dax
sonst ein Fehler bei der Kompilierung auftritt.Im
int d = d + 1;
wird der Zugriff aufd
die lokale Variable fine aufgelöst. Dad
jedoch vor demd
Zugriff noch keine Zuweisung erfolgt ist, gibt der Compiler einen Fehler aus. Inint c = c = 1
,c = 1
passiert zuerst, der Abtretungsempfängerc
, und dannc
auf 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 aufd
jeden Fall von der Zeit zugewiesen wird die endgültiged
erreicht ist.quelle
int b = b + 1
b ist auf der rechten (nicht auf der linken Seite) der Zuordnung, so dass es diese verletzen würde ...int x = x = 1
, in denen In diesem Fall würde nichts davon zutreffen.ist äquivalent zu
während in
Zuerst müssen wir berechnen,
x+1
aber der Wert von x ist nicht bekannt, sodass Sie einen Fehler erhalten (der Compiler weiß, dass der Wert von x nicht bekannt ist).quelle
int x = x = 1;
entsprichtint x = (x = 1)
, nichtx = 1; x = x;
. Sie sollten dafür keine Compiler-Warnung erhalten.int x = x = 1;
s entspricht intx = (x = 1)
wegen der=
int x = (x = 1)
ist äquivalent zuint x; x = 1; x = x;
(Variablendeklaration, Auswertung des Feldinitialisierers , Zuordnung der Variablen zum Ergebnis dieser Auswertung), daher die WarnungEs ist ungefähr gleichbedeutend mit:
Erstens
int <var> = <expression>;
ist immer gleichbedeutend mitIn diesem Fall ist Ihr Ausdruck
x = 1
, was auch eine Aussage ist.x = 1
ist eine gültige Anweisung, da die Variablex
bereits deklariert wurde. Es ist auch ein Ausdruck mit dem Wert 1, der dann erneut zugewiesenx
wird.quelle
0
für Ints, daher würde ich erwarten, dass das Ergebnis 1 ist, nicht dasundefined reference
.x + 1
hat kein definierter Wert, weilx
nicht initialisiert ist.x
jedoch als Mitgliedsvariable definiert ("im Klassenbereich").In Java oder in einer modernen Sprache kommt die Zuordnung von rechts.
Angenommen, Sie haben zwei Variablen x und y,
Diese Anweisung ist gültig und so teilt der Compiler sie auf.
Aber in deinem Fall
Der Compiler hat eine Ausnahme gemacht, weil er sich so aufteilt.
quelle
int x = x = 1;
ist ungleich zu:javap hilft uns wieder, dies sind JVM-Anweisungen, die für diesen Code generiert wurden:
eher wie:
Hier ist kein Grund, einen undefinierten Referenzfehler auszulösen. Es gibt jetzt die Verwendung von Variablen vor ihrer Initialisierung, sodass dieser Code den Spezifikationen vollständig entspricht. Tatsächlich werden Variablen überhaupt nicht verwendet , nur Zuweisungen. Und der JIT-Compiler wird noch weiter gehen und solche Konstruktionen eliminieren. Ehrlich gesagt verstehe ich nicht, wie dieser Code mit der JLS-Spezifikation für die Initialisierung und Verwendung von Variablen verbunden ist. Keine Nutzung keine Probleme. ;)
Bitte korrigieren Sie, wenn ich falsch liege. Ich kann nicht herausfinden, warum andere Antworten, die sich auf viele JLS-Absätze beziehen, so viele Pluspunkte sammeln. Diese Absätze haben mit diesem Fall nichts gemeinsam. Nur zwei Serienzuweisungen und nicht mehr.
Wenn wir schreiben:
entspricht:
Der Ausdruck ganz rechts wird nur den Variablen einzeln ohne Rekursion zugewiesen. Wir können Variablen nach Belieben durcheinander bringen:
quelle
Wenn
int x = x + 1;
Sie 1 zu x hinzufügen, ist der Wert von nochx
nicht erstellt.Aber in
int x=x=1;
wird ohne Fehler kompiliert, weil Sie 1 zuweisenx
.quelle
Ihr erster Code enthält einen zweiten
=
anstelle eines Pluszeichens. Dies wird überall kompiliert, während der zweite Code an keiner Stelle kompiliert wird.quelle
Im zweiten Code wird x vor seiner Deklaration verwendet, während es im ersten Code nur zweimal zugewiesen wird, was keinen Sinn ergibt, aber gültig ist.
quelle
Lassen Sie es uns Schritt für Schritt aufschlüsseln, richtig assoziativ
x = 1
, ordne einer Variablen x 1 zuint x = x
, weisen Sie sich selbst zu, was x ist, als int. Da x zuvor als 1 zugewiesen wurde, behält es 1 bei, wenn auch redundant.Das passt gut zusammen.
x + 1
, füge eins zu einer Variablen x hinzu. Wenn x jedoch undefiniert ist, führt dies zu einem Kompilierungsfehler.int x = x + 1
Daher kompiliert diese Zeile Kompilierungsfehler, da der rechte Teil der Gleichheit nicht kompiliert, indem einer nicht zugewiesenen Variablen eine hinzugefügt wirdquelle
=
Operatoren gibt, also ist es dasselbe wieint x = (x = 1);
.Die zweite
int x=x=1
ist kompiliert, weil Sie den Wert dem x zuweisen, aber in einem anderen Fall wirdint x=x+1
hier die Variable x nicht initialisiert. Denken Sie daran, dass in Java lokale Variablen nicht auf den Standardwert initialisiert werden. Hinweis Wenn es sich auchint x=x+1
im Klassenbereich um ( ) handelt, wird auch ein Kompilierungsfehler ausgegeben, da die Variable nicht erstellt wird.quelle
Kompiliert erfolgreich in Visual Studio 2008 mit Warnung
quelle
c
statt gesehen,java
aber anscheinend war es die andere Frage.bool y;
undy==true
wird false zurückgeben.void main() { int x = x + 1; printf("%d ", x); }
in Visual Studio 2008 kompiliere , erhalte ich beim Debuggen die AusnahmeRun-Time Check Failure #3 - The variable 'x' is being used without being initialized.
und in Release wird die Nummer1896199921
in der Konsole gedruckt.static
Feld (statische Variable auf Klassenebene) dieselben Regeln. Zum Beispiel ein Feld,public static int x = x + 1;
das in Visual C # ohne Warnung als kompiliert deklariert wurde . Möglicherweise das gleiche in Java?x ist nicht initialisiert in
x = x + 1
;Die Java-Programmiersprache ist statisch typisiert. Dies bedeutet, dass alle Variablen zuerst deklariert werden müssen, bevor sie verwendet werden können.
Siehe primitive Datentypen
quelle
Die Codezeile wird nicht mit einer Warnung kompiliert, da der Code tatsächlich funktioniert. Wenn Sie den Code ausführen
int x = x = 1
, erstellt Java zuerst diex
definierte Variable . Dann wird der Zuweisungscode (x = 1
) ausgeführt. Dax
bereits definiert, hat das System keine Fehlereinstellungx
auf 1. Dies gibt den Wert 1 zurück, da dies nun der Wert von istx
. Daherx
wird nun endgültig auf 1 gesetzt.Java führt den Code grundsätzlich so aus, als wäre es das:
In Ihrem zweiten Code muss
int x = x + 1
die+ 1
Anweisungx
jedoch definiert werden, was bis dahin nicht der Fall ist. Da Zuweisungsanweisungen immer bedeuten, dass der Code rechts von=
zuerst ausgeführt wird, schlägt der Code fehl, da er nichtx
definiert ist. Java würde den Code folgendermaßen ausführen:quelle
Complier las die Aussagen von rechts nach links und wir wollten das Gegenteil tun. Deshalb hat es zuerst genervt. Machen Sie es sich zur Gewohnheit, Anweisungen (Code) von rechts nach links zu lesen, damit Sie kein solches Problem haben.
quelle