Also ich würde sagen line
ist effektiv letzten hier, da es nur die Zuordnung in der Schleife und nirgendwo sonst bekommt.
Nein, es ist nicht endgültig, weil es während der Lebensdauer der Variablen einen neuen Wert für jede Schleifeniteration erhält. Dies ist das komplette Gegenteil von Finale.
erhalte ich: ‚Lokale Variablen von einem Lambda-Ausdruck verwiesen wird, müssen endgültig sein oder effektiv endgültig‘. Es erscheint mir ziemlich peinlich.
Betrachten Sie dies: Sie übergeben das Lambda an runLater(...)
. Wenn das Lambda schließlich ausgeführt wird, welchen Wert von line
sollte es verwenden? Der Wert, den es hatte, als das Lambda erstellt wurde, oder der Wert, den es hatte, als das Lambda ausgeführt wurde?
Die Regel ist, dass Lambdas (scheinbar) den aktuellen Wert zum Zeitpunkt der Lambda-Ausführung verwenden. Sie erstellen (scheinbar) keine Kopie der Variablen. Wie wird diese Regel in der Praxis umgesetzt?
Wenn line
ein statisches Feld ist, ist es einfach, weil es kein Staat für das Lambda zu erfassen. Das Lambda kann jederzeit den aktuellen Wert des Feldes lesen, genau wie jeder andere Code auch.
Wenn line
ein Instanzfeld ist, ist das auch ziemlich einfach. Das Lambda kann den Verweis auf das Objekt in einem privaten versteckten Feld in jedem Lambda-Objekt erfassen und über dieses Feld auf das Feld line
zugreifen.
Wenn line
eine lokale Variable in einer Methode ist (wie in Ihrem Beispiel ist), ist dies plötzlich nicht einfach. Auf einer Implementierungsebene ist der Lambda-Ausdruck is in a completely different method, und es gibt keinen einfachen Weg für externen Code, den Zugriff auf die Variable zu teilen, die nur innerhalb der einen Methode existiert.
Um den Zugriff auf die lokale Variable zu aktivieren, würde der Compiler die Variable in einem verborgenen, veränderbare Halterobjekt (wie etwa ein 1-Element-Array), so dass der Halter Objekt sowohl von der referenziert werden zu dem Kasten konnte umschließende Methode und das Lambda, geben ihnen beide Zugriff auf die Variable innerhalb.
Obwohl diese Lösung technisch funktionieren würde, wäre das Verhalten, das sie erreicht, für ein Bündel von Gründen unerwünscht. Das Zuweisen des Halterobjekts würde lokalen Variablen ein unnatürliches Leistungsmerkmal geben, das beim Lesen des Codes nicht offensichtlich wäre. (Das bloße Definieren eines Lambdas, das eine lokale Variable verwendet, würde die Variable in der gesamten Methode langsamer machen.) Schlimmer noch, es würde subtile Race-Bedingungen in ansonsten einfachen Code einführen, abhängig davon, wann das Lambda ausgeführt wird. In Ihrem Beispiel könnte zu dem Zeitpunkt, zu dem das Lambda ausgeführt wird, eine beliebige Anzahl von Schleifeniterationen aufgetreten sein, oder die Methode könnte zurückgegeben worden sein, so dass die line
Variable einen beliebigen Wert oder keinen definierten Wert haben könnte und fast sicher nicht den Wert Sie hätte gewollt. Also in der Praxis würden Sie immer noch brauchen die separate, unveränderliche lineReference
Variable! Der einzige Unterschied ist, dass der Compiler das nicht benötigt, damit Sie kaputten Code schreiben können. Da das Lambda schließlich auf einem anderen Thread ausgeführt werden könnte, würde dies auch zu einer subtilen Parallelitäts- und Threadsichtbarkeitskomplexität für lokale Variablen führen, was erfordern würde, dass die Sprache den Modifizierer auf lokale Variablen und andere Störungen einstellt.
So für das Lambda zu sehen, die aktuellen sich ändernden Werte der lokalen Variablen würde eine Menge Aufwand (und keine Vorteile seit you can do the mutable holder trick manually, wenn Sie jemals brauchen). Stattdessen sagt die Sprache Nein zu der ganzen Kerfufle, indem sie einfach verlangt, dass die Variable final
(oder effektiv endgültig) ist. Auf diese Weise kann das Lambda den Wert der lokalen Variablen zur Lambda-Erzeugungszeit erfassen, und es muss sich nicht darum kümmern, Änderungen zu erkennen, weil es weiß, dass es keine geben kann.
Das ist etwas, der Compiler auch selbst herausfinden konnte
Es war es herauszufinden, weshalb sie es nicht zulässt. Die lineReference
Variable ist von keinerlei Vorteil für den Compiler, der den aktuellen Wert von line
für Verwendung in dem Lambda bei jeder Erstellung des Lambda-Objekts problemlos erfassen konnte. Da das Lambda jedoch keine Änderungen an der Variablen erkennen würde (was aus den oben erläuterten Gründen nicht praktikabel und unerwünscht wäre), wäre der feine Unterschied zwischen der Erfassung von Feldern und der Erfassung von Ortsansässigen verwirrend. Die "final or effective final" -Regel kommt dem Programmierer zugute: Sie verhindert, dass Sie sich wundern, warum Änderungen an einer Variable nicht innerhalb eines Lambda erscheinen, indem Sie sie daran hindern, sie überhaupt zu ändern.Hier ist ein Beispiel dafür, was ohne diese Regel passieren würde:
String field = "A";
void foo() {
String local = "A";
Runnable r =() -> System.out.println(field + local);
field = "B";
local = "B";
r.run(); // output: "BA"
}
Das Verwirrung geht weg, wenn alle lokalen Variablen innerhalb der Lambda-referenziert sind (effektiv) endgültig.
In Ihrem Code ist lineReference
effektiv endgültig. Sein Wert wird während seiner Lebensdauer genau einmal zugewiesen, bevor es am Ende jeder Schleife Iteration aus dem Geltungsbereich hinausgeht, weshalb Sie es im Lambda verwenden können.
Es gibt eine alternative Anordnung der Schleife möglich durch line
innerhalb der Schleife deklarieren:
for (;;) {
String line = bufferedReader.readLine();
if (line == null) break;
runLater(() -> consumeString(line));
}
Dies erlaubt ist, weil line
jetzt außerhalb des Gültigkeitsbereichs am Ende jeder Schleife Iteration geht. Jede Iteration hat effektiv eine frische Variable, die genau einmal zugewiesen ist. (Allerdings ist die Variable auf einem niedrigen Niveau immer noch im selben CPU-Register gespeichert, also ist es nicht so, dass sie wiederholt "erzeugt" und "zerstört" werden muss. Was ich meine, ist, dass es keine zusätzlichen Kosten für die Deklaration von Variablen gibt eine Schleife wie diese, so ist es in Ordnung.)
Hinweis: All dies ist nicht einzigartig für lambdas. Sie gilt auch für alle lexikalisch deklarierten Klassen innerhalb der Methode, von denen lambdas die Regeln geerbt hat.
Anmerkung 2: Es könnte argumentiert werden, dass Lambdas einfacher wären, wenn sie der Regel folgen würden, die Werte von Variablen, die sie zur Zeit der Lambda-Erzeugung verwenden, immer zu erfassen. Dann würde es keinen Unterschied im Verhalten zwischen Feldern und Ortsansässigen geben, und keine Notwendigkeit für die "endgültige oder effektiv endgültige" Regel, weil es gut etabliert wäre, dass Lambdas keine Änderungen nach der Lambda-Erzeugungszeit sehen. Aber diese Regel hätte ihre eigenen Uglines. Als ein Beispiel würde für ein Instanzfeld x
, auf das innerhalb eines Lambda zugegriffen wird, ein Unterschied zwischen dem Verhalten des Lesens x
(Erfassungsendwert von x
) und this.x
(Erfassungsendwert von this
, wobei sein Feld x
sich ändern wird) vorliegen. Sprachdesign ist schwer.
Warum verwenden Sie nicht [BufferedReader :: Linien] (http://docs.oracle.com/javase/8/docs/api/java/io/BufferedReader.html#lines--)? – nosid
@nosid Funktioniert es bei einem Stream, der offen gelassen werden kann? – skiwi
"final" oder "effektiv final" bedeutet, dass der Wert einmal zugewiesen und danach nie mehr geändert wird. 'line' wird mehrfach vergeben (einmal für jedes Mal, wenn eine neue Zeile gelesen wird). Sie müssen eine falsche Vorstellung davon haben, was "effektiv final" bedeutet - es geht nicht darum, wie viele * Plätze * im Quellcode zugewiesen sind, sondern darum, ob es während der Ausführung des Programms neu zugewiesen werden könnte. – ajb