{"id":2733,"date":"2014-03-21T11:52:25","date_gmt":"2014-03-21T09:52:25","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/?p=2733"},"modified":"2014-03-21T11:52:25","modified_gmt":"2014-03-21T09:52:25","slug":"java-8-umgebung-lambda-ausdruecke-variablenzugriffe","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2014\/03\/java-8-umgebung-lambda-ausdruecke-variablenzugriffe\/","title":{"rendered":"Die Umgebung der Lambda-Ausdr\u00fccke und Variablenzugriffe"},"content":{"rendered":"<p>Ein Lambda-Ausdruck \u201esieht\u201c seine Umgebung genauso wie der Code, der vor oder nach dem Lambda-Ausdruck steht. Insbesondere hat ein Lambda-Ausdruck vollst\u00e4ndigen Zugriff auf alle Eigenschaften der Klasse, genauso wie auch der einschlie\u00dfende \u00e4u\u00dfere Block sie hat. Es gibt keinen besonderen Namensraum, sondern nur neue und vielleicht \u00fcberdeckte Variablen durch die Parameter. Das ist einer der grundlegenden Unterschiede zwischen Lambda-Ausdr\u00fccken und inneren Klassen. Somit ist auch die Bedeutung von this und super bei Lambda-Ausdr\u00fccken und inneren Klassen unterschiedlich.<\/p>\n<h4>Zugriff auf finale, lokale Variablen\/Parametervariablen<\/h4>\n<p>Lambda-Ausdr\u00fccke k\u00f6nnen problemlos auf Objektvariablen und Klassenvariablen lesend und schreibend zugreifen. Auch auf lokale Variablen und Parameter hat ein Lambda-Ausdruck Zugriff. Doch greift ein Lambda-Ausdruck auf lokale Variablen bzw. Parametervariablen zu, m\u00fcssen diese final sein. Dass eine Variable final ist, muss nicht extra mit einem Modifizierer geschrieben werden, aber sie muss effektiv final (engl. effectively final) sein. Effektiv final ist eine Variable, wenn sie nach der Initialisierung nicht mehr beschrieben wird.<\/p>\n<p>Ein Beispiel: Der Benutzer soll \u00fcber eine Eingabe die M\u00f6glichkeit bekommen zu bestimmen, ob String-Vergleiche mit unserem trimmenden Comparator unabh\u00e4ngig von der Gro\u00df-\/Kleinschreibung stattfinden sollen.<\/p>\n<pre>public class CompareIgnoreCase {\r\n\u00a0 public static void main( String[] args ) {\r\n\u00a0\u00a0\u00a0 \/*final*\/ boolean <b>compareIgnoreCase<\/b> = new Scanner( System.in ).nextBoolean();\r\n\u00a0\u00a0\u00a0 Comparator&lt;String&gt; c = (s1, s2) -&gt; <b>compareIgnoreCase<\/b> ?\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 s1.trim().compareToIgnoreCase( s2.trim() ) :\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 s1.trim().compareTo( s2.trim() );\r\n\u00a0 \u00a0\u00a0String[] words = { \"M\", \"\\nSkyfall\", \" Q\", \"\\t\\tAdele\\t\" };\r\n\u00a0\u00a0\u00a0 Arrays.sort( words, c );\r\n\u00a0\u00a0\u00a0 System.out.println( Arrays.toString( words ) );\r\n\u00a0 }\r\n}<\/pre>\n<p>Ob compareIgnoreCase von uns final gesetzt wird oder nicht ist egal, denn die Variable wird hier effektiv final verwendet. Nat\u00fcrlich kann es nicht schaden, final als Modifizierer immer davor zu setzen, um dem Leser des Codes diese Tatsache bewusst zu machen.<\/p>\n<p>Neu eingeschobene Lambda-Ausdr\u00fccke, die auf lokale Variablen bzw. Parametervariablen zugreifen, k\u00f6nnen also im Nachhinein zu Compilerfehlern f\u00fchren. Folgendes Segment ist ohne Lambda-Ausdruck korrekt:<\/p>\n<pre>\/*1*\/ boolean compareIgnoreCase = new Scanner( System.in ).nextBoolean();\r\n\/*2*\/ \u2026\r\n\/*3*\/ compareIgnoreCase = true;<\/pre>\n<p>Schiebt sich zwischen Zeile 1 und 3 nachtr\u00e4glich ein Lambda-Ausdruck rein, der auf compareIgnoreCase zugreift, gibt es anschlie\u00dfend einen Compilerfehler. Allerdings liegt der Fehler nicht in Zeile 3, sondern beim Lambda-Ausdruck. Denn die Variable compareIgnoreCase ist nach der \u00c4nderung nicht mehr effektiv final, was sie aber sein m\u00fcsste, um in dem Lambda-Ausdruck verwendet zu werden.<\/p>\n<p>Tipp<\/p>\n<p>Lambda-Ausdr\u00fccke verhalten sich genauso wie innere anonyme Klassen, die auch nur auf finale Variablen zugreifen k\u00f6nnen. Mit Beh\u00e4ltern, wie einem Feld oder den speziellen AtomicXXX-Klassen aus dem java.util.concurrent.atomic-Paket, l\u00e4sst sich das Problem im Prinzip l\u00f6sen. Denn greift ein Lambda-Ausdruck etwa auf das Feld boolean[] compareIgnoreCase = new boolean[1]; zu, so ist die Variable compareIgnoreCase selbst final, aber compareIgnoreCase[0] = true; ist erlaubt und ein Schreibzugriff auf das Feld, nicht der Variablen. Je nach Code besteht jedoch die Gefahr, dass Lambda-Ausdr\u00fccke parallel ausgef\u00fchrt werden. Wird etwa ein Lambda-Ausdruck mit Ver\u00e4nderung auf diesem Feldinhalt parallel ausgef\u00fchrt, so ist der Zugriff nicht synchronisiert und das Ergebnis kann \u201ekaputt\u201c sein, denn paralleler Zugriff auf Variablen muss immer koordiniert vorgenommen werden.<\/p>\n<h4>Namensr\u00e4ume<\/h4>\n<p>Deklariert eine innere anonyme Klasse Variablen innerhalb der Methode, so sind diese immer \u201eneu\u201c, das hei\u00dft, die neuen Variablen \u00fcberlagern vorhandene lokale Variablen aus dem \u00e4u\u00dferen Kontext. Die Variable compareIgnoreCase kann im Rumpf von compare(\u2026) zum Beispiel problemlos neu deklariert werden:<\/p>\n<pre>boolean <b>compareIgnoreCase<\/b> = true;\r\nComparator&lt;String&gt; c = new Comparator&lt;String&gt;() {\r\n\u00a0 @Override public int compare( String s1, String s2 ) {\r\n\u00a0\u00a0 boolean <b>compareIgnoreCase<\/b> = false;\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ v\u00f6llig ok\r\n\u00a0\u00a0 return \u2026\r\n\u00a0 }\r\n};<\/pre>\n<p>In einem Lambda-Ausdruck ist das nicht m\u00f6glich, und folgendes f\u00fchrt zu einer Fehlermeldung des Compilers: \u201evariable compareIgnoreCase ist already defined\u201c.<\/p>\n<pre>boolean <b>compareIgnoreCase<\/b> = true;\r\nComparator&lt;String&gt; c = (s1, s2) -&gt; {\r\n\u00a0 boolean <b>compareIgnoreCase<\/b> = false;\u00a0 \/\/ N Compilerfehler\r\n\u00a0 return \u2026\r\n}<\/pre>\n<h4>this-Referenz<\/h4>\n<p>Ein Lambda-Ausdruck unterscheidet sich von einer inneren (anonymen) Klasse auch in dem, worauf die this-Referenz verweist:<\/p>\n<ul>\n<li>Beim Lambda-Ausdruck zeigt this immer auf das Objekt, in dem der Lambda-Ausdruck eingebettet ist.<\/li>\n<li>Bei einer inneren Klasse referenziert this die innere Klasse, und die ist ein komplett neuer Typ.<\/li>\n<\/ul>\n<p>Folgendes Beispiel macht das deutlich:<\/p>\n<pre>class InnerVsLambdaThis {\r\n<\/pre>\n<pre>\u00a0 InnerVsLambdaThis() {\r\n\u00a0\u00a0\u00a0 Runnable lambdaRun = () -&gt; System.out.println( <b>this<\/b>.getClass().getName() );\r\n\u00a0\u00a0\u00a0 Runnable innerRun\u00a0 = new Runnable() {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 @Override public void run() { System.out.println( <b>this<\/b>.getClass().getName()); }\r\n\u00a0\u00a0\u00a0 };\r\n<\/pre>\n<pre>\u00a0\u00a0\u00a0 lambdaRun.run();\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ InnerVsLambdaThis\r\n\u00a0\u00a0\u00a0 innerRun.run();\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ InnerVsLambdaThis<b>$1\r\n<\/b>  }\r\n\u00a0\r\n\u00a0 public static void main( String[] args ) {\r\n\u00a0\u00a0\u00a0 new InnerVsLambdaThis();\r\n\u00a0 }\r\n}<\/pre>\n<p>Als erstes nutzen wir this in einen Lambda-Ausdruck im Konstruktor der Klasse InnerVsLambdaThis. Damit bezieht sich this auf jedes gebaute InnerVsLambdaThis-Objekt. Bei der inneren Klasse referenziert this ein anderes Exemplar und zwar vom Typ Runnable. Da es bei anonymen Kassen keinen Namen hat, tr\u00e4gt es lediglich die Kennung InnerVsLambdaThis$1.<\/p>\n<h4>Rekursive Lambda-Ausdr\u00fccke<\/h4>\n<p>Lambda-Ausdr\u00fccke k\u00f6nnen auf sich selbst verweisen. Da aber ein this zur Selbstreferenz nicht funktioniert, ist ein kleiner Umweg n\u00f6tig. Erst muss eine Objekt- oder eine Klassenvariable deklariert werden, dann muss dieser Variablen ein Lambda-Ausdruck zugewiesen werden, und dann kann der Lambda-Ausdruck auf diese Variable zugreifen und einen rekursiven Aufruf starten. F\u00fcr den Klassiker der Fakult\u00e4t sieht das so aus:<\/p>\n<pre>public class RecursiveFactLambda {\r\n\u00a0 public static IntFunction&lt;Integer&gt; <b>fact<\/b> = n -&gt; (n == 0) ? 1 : n * <b>fact<\/b>.apply(n-1);\r\n\u00a0 public static void main( String[] args ) {\r\n\u00a0\u00a0\u00a0 System.out.println( fact.apply( 5 ) );\u00a0\u00a0 \/\/ 120\r\n\u00a0 }\r\n}<\/pre>\n<p>IntFunction ist eine funktionale Schnittstelle aus dem Paket java.util.function mit einer Operation T apply(int i). T ist ein generischer R\u00fcckgabetyp, den wir hier mit Integer belegt haben.<\/p>\n<p>fact h\u00e4tte genauso gut als normale Methode deklariert werden k\u00f6nnen. Gro\u00dfartige Vorteile bietet die Schreibweise mit Lambda-Ausdr\u00fccken hier nicht. Zumal jetzt auch der Begriff \u201eanonyme Methode\u201c nicht mehr so richtig passt, da der Lambda-Ausdruck ja doch einen Namen hat, n\u00e4mlich fact. Und weil der Lambda-Ausdruck einer Variablen zugewiesen wurde, kann er in dieser Form nat\u00fcrlich auch nicht mehr als Implementierung an eine Methode oder einen Konstruktor \u00fcbergeben werden, sondern nur als Methoden\/Konstruktor-Referenz, dazu sp\u00e4ter mehr.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ein Lambda-Ausdruck \u201esieht\u201c seine Umgebung genauso wie der Code, der vor oder nach dem Lambda-Ausdruck steht. Insbesondere hat ein Lambda-Ausdruck vollst\u00e4ndigen Zugriff auf alle Eigenschaften der Klasse, genauso wie auch der einschlie\u00dfende \u00e4u\u00dfere Block sie hat. Es gibt keinen besonderen Namensraum, sondern nur neue und vielleicht \u00fcberdeckte Variablen durch die Parameter. Das ist einer der [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[11,66],"tags":[],"class_list":["post-2733","post","type-post","status-publish","format-standard","hentry","category-insel","category-java-8"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/2733","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=2733"}],"version-history":[{"count":2,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/2733\/revisions"}],"predecessor-version":[{"id":2735,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/2733\/revisions\/2735"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=2733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=2733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=2733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}