Lambdas: Lokale Variablen brauchen final, Instanzvariablen nicht

Lesezeit: 8 Minuten

Lambdas Lokale Variablen brauchen final Instanzvariablen nicht
Gerhard

In einem Lambda müssen lokale Variablen final sein, Instanzvariablen jedoch nicht. Warum so?

  • Verwandte: Warum sind in der anonymen Klasse nur finale Variablen zugänglich?

    – McDowell

    31. Juli 14 um 9:33 Uhr

  • Lassen Sie uns wissen, dass zumindest mit der neuesten Version des Compilers Java 1.8 lokale Variablen nur effektiv final sein müssen, sodass sie nicht per se als final deklariert werden müssen, aber nicht geändert werden können.

    – Valentin Ruano

    6. Juni 18 um 20:18 Uhr

  • Nachdem ich alle Antworten hier gelesen habe, denke ich immer noch, dass es nur eine vom Compiler erzwungene Regel ist, die Programmierfehler minimieren soll – das heißt, es gibt keinen technischen Grund, warum veränderliche lokale Variablen nicht erfasst werden können oder warum erfasste lokale Variablen dies können. nicht mutiert sein. Dieser Punkt wird durch die Tatsache unterstützt, dass diese Regel leicht umgangen werden kann, indem ein Objekt-Wrapper verwendet wird (also ist die Objektreferenz effektiv endgültig, aber nicht das Objekt selbst). Eine andere Möglichkeit besteht darin, ein Array zu erstellen, dh Integer[] count = {new Integer(5)}. Siehe auch stackoverflow.com/a/50457016/7154924.

    – flow2k

    4. Oktober 18 um 8:05 Uhr


  • @McDowell, Lambdas sind nicht nur Syntaxzucker für anonyme Klassen, sondern ein ganz anderes Konstrukt.

    – Schrittmacher

    15. Mai 20 um 23:17 Uhr

1644075490 867 Lambdas Lokale Variablen brauchen final Instanzvariablen nicht
Adam Adamaszek

Der grundlegende Unterschied zwischen einem Feld und einer lokalen Variablen besteht darin, dass die lokale Variable ist kopiert wenn JVM eine Lambda-Instanz erstellt. Andererseits können Felder frei geändert werden, da die Änderungen an ihnen auch an die externe Klasseninstanz (ihre Umfang ist die gesamte Außenseiterklasse, wie Boris unten betonte).

Die einfachste Art, über anonyme Klassen, Closures und Labmdas nachzudenken, stammt aus der variabler Umfang Perspektive; Stellen Sie sich einen Kopierkonstruktor vor, der für alle lokalen Variablen hinzugefügt wird, die Sie an eine Closure übergeben.

  • Richtig, dann muss der anonyme Klassenkonstruktor die Instanzvariable nicht kopieren, weil er sie einfach referenzieren kann. Gute Erklärung!

    – Gerhard

    31. Juli 14 um 9:49 Uhr

Lambdas Lokale Variablen brauchen final Instanzvariablen nicht
Kein Fehler

Im Dokument des Projekts Lambda: Zustand des Lambda v4

Unter Abschnitt 7. VariablenerfassungEs wird erwähnt, dass ….

Es ist unsere Absicht, die Erfassung veränderlicher lokaler Variablen zu verbieten. Der Grund dafür sind Redewendungen wie diese:

int sum = 0;
list.forEach(e -> { sum += e.size(); });

sind grundsätzlich seriell; Es ist ziemlich schwierig, solche Lambda-Körper zu schreiben, die keine Race-Bedingungen haben. Wenn wir nicht bereit sind – vorzugsweise zur Kompilierzeit – durchzusetzen, dass eine solche Funktion ihrem einfangenden Thread nicht entkommen kann, kann dieses Feature durchaus mehr Probleme verursachen, als es löst.

Bearbeiten :

Eine weitere Sache, die hier zu beachten ist, ist, dass lokale Variablen im Konstruktor der inneren Klasse übergeben werden, wenn Sie innerhalb Ihrer inneren Klasse darauf zugreifen, und dies funktioniert nicht mit nicht finalen Variablen, da der Wert von nicht finalen Variablen nach der Konstruktion geändert werden kann.

Während im Falle einer Instanzvariable der Compiler die Referenz der Klasse übergibt und die Referenz der Klasse verwendet wird, um auf die Instanzvariable zuzugreifen. Daher ist es bei Instanzvariablen nicht erforderlich.

PS: Es ist erwähnenswert, dass anonyme Klassen nur auf finale lokale Variablen zugreifen können (in JAVA SE 7), während Sie in Java SE 8 effektiv auf finale Variablen auch innerhalb von Lambda sowie inneren Klassen zugreifen können.

In Java 8 in Aktion Buch wird diese Situation wie folgt erklärt:

Sie fragen sich vielleicht, warum lokale Variablen diese Einschränkungen haben. Erstens gibt es einen wesentlichen Unterschied darin, wie Instanz- und lokale Variablen hinter den Kulissen implementiert werden. Instanzvariablen werden auf dem Heap gespeichert, während lokale Variablen auf dem Stack leben. Wenn ein Lambda direkt auf die lokale Variable zugreifen könnte und das Lambda in einem Thread verwendet würde, könnte der Thread, der das Lambda verwendet, versuchen, auf die Variable zuzugreifen, nachdem der Thread, der die Variable zugewiesen hat, sie freigegeben hat. Daher implementiert Java den Zugriff auf eine freie lokale Variable als Zugriff auf eine Kopie davon und nicht als Zugriff auf die ursprüngliche Variable. Dies macht keinen Unterschied, wenn die lokale Variable nur einmal zugewiesen wird – daher die Einschränkung. Zweitens hält diese Einschränkung auch von typischen imperativen Programmiermustern ab (die, wie wir in späteren Kapiteln erklären, eine einfache Parallelisierung verhindern), die eine äußere Variable verändern.

  • Ich denke wirklich, dass es einige Probleme gibt Java 8 in Aktion in diesem Punkt. Wenn die lokale Variable bezieht sich hier auf die Variablen, die in der Methode erstellt wurden, auf die aber von den Lambdas zugegriffen wird, und der Multi-Thread wird erreicht durch ForkJoindann wird es eine Kopie für verschiedene Threads geben und Mutationen in Lambdas sind theoretisch akzeptabel, in diesem Fall kann dies der Fall sein mutiert. Aber die Sache hier ist anders, lokale Variablen verwendet in Lambda ist für Parallelisierung erreicht durch parallelStreamund diese lokale Variablen werden von verschiedenen Threads geteilt, die auf dem basieren Lambdas.

    – Hören

    12. Mai 18 um 5:50 Uhr


  • Also die erster Punkt ist eigentlich nicht richtig, es gibt keine sogenannte Kopie, wird es von Threads in parallelStream geteilt. Und die gemeinsame Nutzung veränderlicher Variablen zwischen Threads ist genauso gefährlich wie die Zweiter Punkt. Aus diesem Grund verhindern wir dies und führen integrierte Methoden in Stream ein, um diese Fälle zu behandeln.

    – Hören

    12. Mai 18 um 5:51 Uhr

Da auf Instanzvariablen immer über eine Feldzugriffsoperation auf einen Verweis auf ein Objekt zugegriffen wird, dh some_expression.instance_variable. Auch wenn Sie nicht explizit über die Punktnotation darauf zugreifen, wie z instance_variablewird es implizit behandelt als this.instance_variable (oder wenn Sie in einer inneren Klasse auf die Instanzvariable einer äußeren Klasse zugreifen, OuterClass.this.instance_variabledie sich unter der Haube befindet this.<hidden reference to outer this>.instance_variable).

Daher wird niemals direkt auf eine Instanzvariable zugegriffen, und die echte “Variable”, auf die Sie direkt zugreifen, ist es this (was “effektiv endgültig” ist, da es nicht zuweisbar ist) oder eine Variable am Anfang eines anderen Ausdrucks.

Einige Konzepte für zukünftige Besucher aufstellen:

Im Grunde läuft alles darauf hinaus Der Compiler sollte in der Lage sein, deterministisch zu erkennen, dass der Körper des Lambda-Ausdrucks nicht an einer veralteten Kopie der Variablen arbeitet.

Bei lokalen Variablen hat der Compiler keine Möglichkeit sicherzustellen, dass der Körper des Lambda-Ausdrucks nicht an einer veralteten Kopie der Variablen arbeitet, es sei denn, diese Variable ist final oder effektiv final, daher sollten lokale Variablen entweder final oder effektiv final sein.

Wenn Sie nun im Fall von Instanzfeldern auf ein Instanzfeld innerhalb des Lambda-Ausdrucks zugreifen, hängt der Compiler a an this auf diesen Variablenzugriff (falls Sie es nicht explizit getan haben) und seitdem this ist effektiv final, sodass der Compiler sicher ist, dass der Hauptteil des Lambda-Ausdrucks immer die neueste Kopie der Variablen enthält (bitte beachten Sie, dass Multithreading für diese Diskussion derzeit nicht berücksichtigt wird). Im Fall von Instanzfeldern kann der Compiler also feststellen, dass der Lambda-Körper die neueste Kopie der Instanzvariablen hat, sodass Instanzvariablen nicht endgültig oder effektiv endgültig sein müssen. Bitte beachten Sie den folgenden Screenshot einer Oracle-Folie:

Geben Sie hier die Bildbeschreibung ein

Bitte beachten Sie auch, dass Sie möglicherweise Probleme bekommen, wenn Sie auf ein Instanzfeld in einem Lambda-Ausdruck zugreifen und dieses in einer Multithread-Umgebung ausgeführt wird.

  • Würde es Ihnen etwas ausmachen, die Quelle der Oracle-Folie anzugeben?

    – flow2k

    4. Oktober 18 um 8:06 Uhr

  • @hagrawal könnten Sie bitte Ihre abschließende Aussage zur Multithread-Umgebung erläutern? Bezieht es sich jederzeit auf den tatsächlichen Wert der Mitgliedsvariablen, da viele Threads gleichzeitig ausgeführt werden, sodass sie die Instanzvariable überschreiben können. Auch wenn ich die Member-Variablen richtig synchronisiere, bleibt das Problem dann auch bestehen?

    – Yug Singh

    29. Oktober 18 um 20:16 Uhr


  • Beste Antwort auf die Frage, denke ich;)

    – Supun Wijerathne

    6. Dezember 18 um 5:37 Uhr

Lambdas Lokale Variablen brauchen final Instanzvariablen nicht
Boris die Spinne

Es scheint, als würden Sie nach Variablen fragen, auf die Sie von einem Lambda-Körper aus verweisen können.

Von dem JLS §15.27.2

Alle lokalen Variablen, formalen Parameter oder Ausnahmeparameter, die verwendet, aber nicht in einem Lambda-Ausdruck deklariert werden, müssen entweder als final deklariert werden oder effektiv final sein (§4.12.4), oder es tritt ein Kompilierzeitfehler auf, wenn die Verwendung versucht wird.

Sie müssen also keine Variablen als deklarieren final Sie müssen nur sicherstellen, dass sie “effektiv endgültig” sind. Dies ist die gleiche Regel wie für anonyme Klassen.

  • Würde es Ihnen etwas ausmachen, die Quelle der Oracle-Folie anzugeben?

    – flow2k

    4. Oktober 18 um 8:06 Uhr

  • @hagrawal könnten Sie bitte Ihre abschließende Aussage zur Multithread-Umgebung erläutern? Bezieht es sich jederzeit auf den tatsächlichen Wert der Mitgliedsvariablen, da viele Threads gleichzeitig ausgeführt werden, sodass sie die Instanzvariable überschreiben können. Auch wenn ich die Member-Variablen richtig synchronisiere, bleibt das Problem dann auch bestehen?

    – Yug Singh

    29. Oktober 18 um 20:16 Uhr


  • Beste Antwort auf die Frage, denke ich;)

    – Supun Wijerathne

    6. Dezember 18 um 5:37 Uhr

Innerhalb von Lambda-Ausdrücken können Sie effektiv finale Variablen aus dem umgebenden Bereich verwenden. Effektiv bedeutet, dass es nicht zwingend erforderlich ist, die Variable final zu deklarieren, aber stellen Sie sicher, dass Sie ihren Zustand innerhalb des Lambda-Ausdrucks nicht ändern.

Sie können dies auch innerhalb von Closures verwenden, und die Verwendung von “this” bedeutet das einschließende Objekt, aber nicht das Lambda selbst, da Closures anonyme Funktionen sind und ihnen keine Klasse zugeordnet ist.

Wenn Sie also ein beliebiges Feld (z. B. private Integer i;) aus der umschließenden Klasse verwenden, das nicht als endgültig und nicht effektiv als endgültig deklariert ist, funktioniert es immer noch, da der Compiler den Trick in Ihrem Namen macht und “this” (this.i) einfügt. .

private Integer i = 0;
public  void process(){
    Consumer<Integer> c = (i)-> System.out.println(++this.i);
    c.accept(i);
}

.

784900cookie-checkLambdas: Lokale Variablen brauchen final, Instanzvariablen nicht

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy