Schauen Sie sich die folgende while
Endlosschleife in Java an. Es verursacht einen Kompilierungsfehler für die Anweisung darunter.
while(true) {
System.out.println("inside while");
}
System.out.println("while terminated"); //Unreachable statement - compiler-error.
Die folgende while
Endlosschleife funktioniert jedoch einwandfrei und gibt keine Fehler aus, bei denen ich die Bedingung nur durch eine boolesche Variable ersetzt habe.
boolean b=true;
while(b) {
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
Auch im zweiten Fall ist die Anweisung nach der Schleife offensichtlich nicht erreichbar, da die boolesche Variable b
wahr ist und der Compiler sich überhaupt nicht beschwert. Warum?
Bearbeiten: Die folgende Version von while
bleibt als offensichtlich in einer Endlosschleife stecken, gibt jedoch keine Compilerfehler für die Anweisung darunter aus, obwohl die if
Bedingung innerhalb der Schleife immer ist false
und folglich die Schleife niemals zurückkehren kann und vom Compiler am festgelegt werden kann Kompilierungszeit selbst.
while(true) {
if(false) {
break;
}
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
while(true) {
if(false) { //if true then also
return; //Replacing return with break fixes the following error.
}
System.out.println("inside while");
}
System.out.println("while terminated"); //Compiler-error - unreachable statement.
while(true) {
if(true) {
System.out.println("inside if");
return;
}
System.out.println("inside while"); //No error here.
}
System.out.println("while terminated"); //Compiler-error - unreachable statement.
Edit: Gleiches mit if
und while
.
if(false) {
System.out.println("inside if"); //No error here.
}
while(false) {
System.out.println("inside while");
// Compiler's complain - unreachable statement.
}
while(true) {
if(true) {
System.out.println("inside if");
break;
}
System.out.println("inside while"); //No error here.
}
Die folgende Version von while
bleibt auch in einer Endlosschleife stecken.
while(true) {
try {
System.out.println("inside while");
return; //Replacing return with break makes no difference here.
} finally {
continue;
}
}
Dies liegt daran, dass der finally
Block immer ausgeführt wird, obwohl die return
Anweisung innerhalb des try
Blocks selbst auf ihn trifft .
quelle
Antworten:
Der Compiler kann leicht und eindeutig beweisen, dass der erste Ausdruck immer zu einer Endlosschleife führt, für den zweiten jedoch nicht so einfach. In Ihrem Spielzeugbeispiel ist es einfach, aber was ist, wenn:
Der Compiler sucht eindeutig nicht nach Ihrem einfacheren Fall, da er auf diese Straße insgesamt verzichtet. Warum? Weil es durch die Spezifikation
viel schwierigerverboten ist. Siehe Abschnitt 14.21 :(By the way, mein Compiler nicht beschweren , wenn die Variable deklariert ist
final
.)quelle
Gemäß den Spezifikationen wird das Folgende über while-Anweisungen gesagt.
Der Compiler sagt also nur, dass der Code nach einer while-Anweisung nicht erreichbar ist, wenn die while-Bedingung eine Konstante mit einem wahren Wert ist oder innerhalb der while-Anweisung eine break-Anweisung vorhanden ist. Im zweiten Fall wird der darauf folgende Code nicht als nicht erreichbar angesehen, da der Wert von b keine Konstante ist. Hinter diesem Link stecken viel mehr Informationen, um Ihnen mehr Details darüber zu geben, was nicht erreichbar ist und was nicht.
quelle
Weil true konstant ist und b in der Schleife geändert werden kann.
quelle
Da die Analyse des variablen Status schwierig ist, hat der Compiler so gut wie aufgegeben und Sie können tun, was Sie möchten. Darüber hinaus enthält die Java-Sprachspezifikation klare Regeln dafür, wie der Compiler nicht erreichbaren Code erkennen darf .
Es gibt viele Möglichkeiten, den Compiler auszutricksen - ein weiteres häufiges Beispiel ist
Das würde nicht funktionieren, da der Compiler erkennen würde, dass der Bereich nicht erreichbar ist. Stattdessen könnten Sie tun
Dies würde funktionieren, da der Compiler nicht erkennen kann, dass der Ausdruck niemals falsch sein wird.
quelle
if
sich ein wenig von awhile
, da der Compiler sogar die Sequenz kompiliert,if(true) return; System.out.println("Hello");
ohne sich zu beschweren. IIRC, dies ist eine besondere Ausnahme in der JLS. Der Compiler wäre in der Lage, den nicht erreichbaren Code nach demif
so einfach wie mit zu erkennenwhile
.Letzteres ist nicht unerreichbar. Der boolesche Wert b hat immer noch die Möglichkeit, irgendwo innerhalb der Schleife in false geändert zu werden, was zu einer Endbedingung führt.
quelle
Ich vermute, dass die Variable "b" die Möglichkeit hat, ihren Wert zu ändern, damit der Compiler denken
System.out.println("while terminated");
kann.quelle
Compiler sind nicht perfekt - und sollten es auch nicht sein
Der Compiler ist dafür verantwortlich, die Syntax zu bestätigen und nicht die Ausführung zu bestätigen. Compiler können letztendlich viele Laufzeitprobleme in einer stark typisierten Sprache abfangen und verhindern - aber sie können nicht alle derartigen Fehler abfangen.
Die praktische Lösung besteht darin, Batterien mit Komponententests zu haben, um Ihre Compilerprüfungen zu ergänzen, ODER objektorientierte Komponenten für die Implementierung von Logik zu verwenden, die als robust bekannt sind, anstatt sich auf primitive Variablen und Stoppbedingungen zu verlassen.
Starke Typisierung und OO: Steigerung der Effizienz des Compilers
Einige Fehler sind syntaktischer Natur - und in Java macht die starke Typisierung viele Laufzeitausnahmen abfangbar. Durch die Verwendung besserer Typen können Sie Ihrem Compiler jedoch helfen, eine bessere Logik durchzusetzen.
Wenn der Compiler die Logik in Java effektiver erzwingen soll, besteht die Lösung darin, robuste, erforderliche Objekte zu erstellen, die diese Logik erzwingen können, und diese Objekte zum Erstellen Ihrer Anwendung anstelle von Grundelementen zu verwenden.
Ein klassisches Beispiel hierfür ist die Verwendung des Iteratormusters in Kombination mit der foreach-Schleife von Java. Dieses Konstrukt ist weniger anfällig für den von Ihnen dargestellten Fehlertyp als eine vereinfachte while-Schleife.
quelle
Der Compiler ist nicht hoch genug, um die
b
möglicherweise enthaltenen Werte zu durchlaufen (obwohl Sie ihn nur einmal zuweisen). Das erste Beispiel ist für den Compiler leicht zu erkennen, dass es sich um eine Endlosschleife handelt, da die Bedingung nicht variabel ist.quelle
Ich bin überrascht, dass Ihr Compiler sich geweigert hat, den ersten Fall zu kompilieren. Das kommt mir komisch vor.
Der zweite Fall ist jedoch nicht für den ersten Fall optimiert, da (a) ein anderer Thread den Wert von
b
(b) die aufgerufene Funktion möglicherweise den Wert vonb
als Nebeneffekt ändert .quelle
Eigentlich glaube ich nicht, dass irgendjemand es ganz richtig verstanden hat (zumindest nicht im Sinne des ursprünglichen Fragestellers). Das OQ erwähnt immer wieder:
Aber es spielt keine Rolle, da die letzte Zeile erreichbar ist. Wenn Sie diesen Code genommen, in eine Klassendatei kompiliert und die Klassendatei an eine andere Person (z. B. als Bibliothek) übergeben haben, kann diese die kompilierte Klasse mit Code verknüpfen, der "b" durch Reflexion ändert, die Schleife verlässt und die letzte verursacht Zeile auszuführen.
Dies gilt für alle Variablen, die keine Konstante sind (oder final, die an dem Ort, an dem sie verwendet werden, zu einer Konstanten kompiliert werden. Dies führt manchmal zu bizarren Fehlern, wenn Sie die Klasse mit dem final neu kompilieren und nicht mit einer Klasse, die darauf verweist, der Referenzierung Die Klasse behält weiterhin den alten Wert ohne Fehler bei.
Ich habe die Fähigkeit der Reflexion genutzt, um nicht endgültige private Variablen einer anderen Klasse zu ändern, um eine Klasse in einer gekauften Bibliothek mit Affen zu patchen. So wurde ein Fehler behoben, damit wir uns weiterentwickeln konnten, während wir auf offizielle Patches des Anbieters warteten.
Übrigens funktioniert dies heutzutage möglicherweise nicht mehr - obwohl ich es bereits getan habe, besteht die Möglichkeit, dass eine so kleine Schleife im CPU-Cache zwischengespeichert wird, und da die Variable nicht als flüchtig markiert ist, wird der zwischengespeicherte Code möglicherweise nie zwischengespeichert nimm den neuen Wert auf. Ich habe das noch nie in Aktion gesehen, aber ich glaube, es ist theoretisch wahr.
quelle
b
eine Methodenvariable ist. Können Sie eine Methodenvariable mithilfe von Reflektion wirklich ändern? Unabhängig davon ist der allgemeine Punkt, dass wir nicht davon ausgehen sollten, dass die letzte Linie nicht erreichbar ist.Es ist einfach, weil der Compiler nicht zu viel Babysitting-Arbeit macht, obwohl es möglich ist.
Das gezeigte Beispiel ist für den Compiler einfach und sinnvoll, um die Endlosschleife zu erkennen. Aber wie wäre es, wenn wir 1000 Codezeilen ohne Beziehung zur Variablen einfügen
b
? Und wie wäre es mit diesen Aussagenb = true;
? Der Compiler kann das Ergebnis definitiv auswerten und Ihnen sagen, dass es irgendwann in einerwhile
Schleife wahr ist , aber wie langsam wird es sein, ein reales Projekt zu kompilieren?PS, Fusselwerkzeug sollte es auf jeden Fall für Sie tun.
quelle
Aus Sicht der Compiler die
b
inwhile(b)
könnte falsch irgendwo ändern. Der Compiler macht sich einfach nicht die Mühe zu überprüfen.Zum Spaß versuchen
while(1 < 2)
,for(int i = 0; i < 1; i--)
etc.quelle
Ausdrücke werden zur Laufzeit ausgewertet. Wenn Sie also den Skalarwert "true" durch eine boolesche Variable ersetzen, haben Sie einen Skalarwert in einen booleschen Ausdruck geändert, sodass der Compiler ihn zur Kompilierungszeit nicht erkennen kann.
quelle
Wenn der Compiler endgültig feststellen kann, dass der Boolesche Wert zur
true
Laufzeit ausgewertet wird, wird dieser Fehler ausgegeben. Der Compiler geht davon aus, dass die Variable , die Sie deklariert kann geändert werden (wenn auch wir hier als Menschen wissen es nicht).Um diese Tatsache zu betonen: Wenn Variablen wie
final
in Java deklariert werden , geben die meisten Compiler denselben Fehler aus, als hätten Sie den Wert ersetzt. Dies liegt daran, dass die Variable zur Kompilierungszeit definiert ist (und zur Laufzeit nicht geändert werden kann) und der Compiler daher endgültig bestimmen kann, wie der Ausdruck zurtrue
Laufzeit ausgewertet wird.quelle
Die erste Anweisung führt immer zu einer Endlosschleife, da wir eine Konstante im Zustand der while-Schleife angegeben haben, wobei der Compiler wie im zweiten Fall davon ausgeht, dass die Möglichkeit besteht, den Wert von b innerhalb der Schleife zu ändern.
quelle