Closures in Java [] (Closures in Java), Lektion, Seite 722844
https://www.purl.org/stefan_ram/pub/closures_java (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Pseudoclosures in Java 

closure /ˈkloʊʒɚ/ — anything that closes

Wir zeigen die mit Einschlüssen zusammenhängenden Phänomen in dieser Lektion der Kürze halber zunächst mit Hilfe von Methodenliteralen, jedoch kann man diese auch auf benannte Methoden in Klassen, deren Deklaration sich in einer Methode befindet übertragen.

Die Lebensdauer von Aktivierungsverbunden

Wenn eine Methode aktiviert wird, dann werden ihre Parameter und lokalen Variablen in einem Teil des Speicher angelegt, der Aktivierungsverbund (activation record, stack frame) genannt wird.

Nach dem Ende der Aktivität (Inkarnation) der Methode, wird dieser Aktivierungsverbund wieder aufgelöst. Daher ist es nach dem Ende einer Inkarnation nicht mehr möglich, direkt auf Variablen des Aktivierungsverbundes zuzugreifen.

Das folgende Programm zeigt, daß beim zweiten Aufruf der Methode »run()« ein neuer Aktivierungsverbund angelegt wird, der Wert »27«, welche beim ersten Aufruf nach »a« geschrieben wurde befindet sich nicht in dem Aktivierungsverbund der zweiten Inkarnation.

Main.java

public final class Main
{ public static boolean first = true;

public static void run()
{ double a = 0;

if( first ){ a = 27; first = false; }
else java.lang.System.out.println( a ); }

public static void main( final java.lang.String[] args )
{ Main.run(); Main.run(); }}

transcript
0.0

Einschluß von Werten in Methodenliteralen

Wenn der Wert einer Variablen aus einem Aktivierungsverbund in einem Methodenliteral verwendet wird, dann wird die Lebensdauer dieser Variablen aber von der Lebensdauer ihrer ursprünglichen Deklaration auf die des Wertes des Methodenliterals verlängert.

Main.java

public final class Main
{ public static boolean first = true;

public static java.util.function.Function< java.lang.Integer, java.lang.Integer >add( final int a )
{ return x -> a + x; }

public static void main( final java.lang.String[] args )
{ final java.util.function.Function< java.lang.Integer, java.lang.Integer >add2 = add( 2 );
final java.util.function.Function< java.lang.Integer, java.lang.Integer >add5 = add( 5 );
java.lang.System.out.println( add2.apply( 10 ));
java.lang.System.out.println( add2.apply( 20 ));
java.lang.System.out.println( add5.apply( 10 ));
java.lang.System.out.println( add5.apply( 20 )); }}

transcript
12
22
15
25

Der Wert des Parameters »a« ist in den Objekten »add2« und »add5« „eingeschlossen“ worden und existiert damit auch noch nach Beendigung des Aufrufs »add( 2 )« und »add( 5 )«. Daher nennt man solche Objekte auch “closures ” (Einschlüsse).

»final«

Java  verlangt jedoch, daß die eingeschlossenen Bezeichner mit »final« deklariert werden oder im Programm wenigstens so behandelt werden, als seien sie mit »final« deklariert worden (“effectively final ”).

Wegen dieser Einschränkung sind die Closures von Java keine „richtigen“ Closures, und wir nennen sie daher auch „Pseudoclosures“.

Main.java

public final class Main
{ public static boolean first = true;

public static java.util.function.Function< java.lang.Integer, java.lang.Integer >add( int a )
{ return x -> a + x; }

public static void main( final java.lang.String[] args )
{ final java.util.function.Function< java.lang.Integer, java.lang.Integer >add2 = add( 2 );
final java.util.function.Function< java.lang.Integer, java.lang.Integer >add5 = add( 5 );
java.lang.System.out.println( add2.apply( 10 ));
java.lang.System.out.println( add2.apply( 20 ));
java.lang.System.out.println( add5.apply( 10 ));
java.lang.System.out.println( add5.apply( 20 )); }}

transcript
12
22
15
25
Main.java

public final class Main
{ public static boolean first = true;

public static java.util.function.Function< java.lang.Integer, java.lang.Integer >add( int a )
{ a = 3; return x -> a + x; }

public static void main( final java.lang.String[] args )
{ final java.util.function.Function< java.lang.Integer, java.lang.Integer >add2 = add( 2 );
final java.util.function.Function< java.lang.Integer, java.lang.Integer >add5 = add( 5 );
java.lang.System.out.println( add2.apply( 10 ));
java.lang.System.out.println( add2.apply( 20 ));
java.lang.System.out.println( add5.apply( 10 ));
java.lang.System.out.println( add5.apply( 20 )); }}

transcript
Main.java:5: error: local variables referenced from a lambda expression must be final or effectively final
{ a = 3; return x -> a + x; }
^
1 error

Warum wird dies verlangt? Angeblich hätten sich Java -Anwender dies so gewünscht!

“Actually, the prototype implementation *did* allow non-final variables to be referenced from within inner classes. There was an outcry from *users*, complaining that they did not want this! The reason was interesting: in order to support such variables, it was necessary to heap-allocate them, and (at that time, at least) the average Java programmer was still pretty skittish about heap allocation and garbage collection and all that. They disapproved of the language performing heap allocation "under the table" when there was no occurrence of the "new" keyword in sight.
Maybe we could lift that restriction in the next few years...
I dunno.”
“One of the early design principles of Java was that heap allocation occurs if and only if a construct involving the "new" keyword is executed. Adding full-blown closures violated this design principle. (Dynamic class loading also violated the principle, but users seemed to be comfortable with that, perhaps because they believed that "once the computation got going" no more heap allocation would occur.)
Other features for Java release 1.5 will perform certain kinds of autoboxing, which also violates the design principle. This fact will make it easier to argue for restoring full-blown support for closures in the future.”
Guy Steel  zitiert nach http://madbean.com/2003/mb2003-49/

In einigen anderen Programmiersprachen gibt es „richtige Closures“, welche auch veränderliche Variablen aus dem Aktivierungsverbund mit einschließen.

Die Methodenausdrücke von Java  werden von manchen Programmierern als „Lambda-Ausdrücke“ bezeichnet, weil Methodenliterale in manchen anderen  Programmiersprachen mit einem Lambda geschrieben werden, oder als „Closures“, weil Methodenliterale in manchen anderen  Programmiersprachen Closures mit Einschluß von Variablen aus dem Aktivierungsverbund sind. In Java  werden Methodenliterale aber nicht mit einem Lambda geschrieben und sie sind auch keine richtigen Closures. Daher sind beide Bezeichnungen unpassend.

Man sollte beachten, daß das »final« nur für die Variable selber gelten muß. Es spricht nichts dagegen, daß sie ein veränderliches Objekt referenziert. Außerdem gilt diese Anforderung nur für Parameter und lokale Variablen, nicht für Felder (Klassen- und Exemplarvariablen) aus der Umgebung eines Methodenliterals.

final-Einschlüsse (final-Closures ) in Exemplarerzeugungsausdrücken

Das folgende Programmbeispiel zeigt den „Einschluß“ einer lokalen Variablen »i« (eigentlich ein Parameter) der Methode »f« in zwei Objekte »c0« und »c1«. Man sieht, daß diese Variable auch nach dem Ende der Inkarnation ihrer Methode  in den Objekten weiterlebt. Solch eine Variable muß aber mit »final« deklariert worden sein.

Main.java

public final class Main
{

public static java.lang.Runnable f( final int i )
{ return new java.lang.Runnable()
{ public void run()
{ java.lang.System.out.println( i ); }}; }

public static void main( final java.lang.String[] args )
{ final java.lang.Runnable c0 = f( 0 );
final java.lang.Runnable c1 = f( 1 );
c0.run();
c1.run(); }}

java.lang.System.out
0
1

The scope of a declaration is the region of the program within which the entity declared by the declaration can be referred to using a simple name, provided it is visible.” — JLS7, 6.3.

The scope of a local variable declaration in a block is the rest of the block in which the declaration appears, starting with its own initializer and including any further declarators to the right in the local variable declaration statement.” — JLS7, 6.3.

local variables from the surrounding method may be referred to ” — JLS7, 8.1.3, Example 8.1.3-2.

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722844 stefan_ram:722844 Closures in Java Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722844, slrprddef722844, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/closures_java