{"id":1632,"date":"2012-12-24T12:27:45","date_gmt":"2012-12-24T10:27:45","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/?p=1632"},"modified":"2017-08-10T17:09:02","modified_gmt":"2017-08-10T15:09:02","slug":"funktionale-schnittstellen-in-java-8","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2012\/12\/funktionale-schnittstellen-in-java-8\/","title":{"rendered":"Funktionale Schnittstellen und Typ-Inferenz in Java 8"},"content":{"rendered":"<p>In unserem Beispiel haben wir den Lambda-Ausdruck als Argument von Array.sort(\u2026) eingesetzt:<\/p>\n<pre>Arrays.sort(\u00a0words,\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(String\u00a0s1,\u00a0String\u00a0s2)\u00a0-&gt;\u00a0{\u00a0return\u00a0s1.trim().compareTo(s2.trim());\u00a0}\u00a0);<\/pre>\n<p>Wir h\u00e4tten aber auch den Lambda-Ausdruck explizit einer lokalen Variablen zuweisen k\u00f6nnen, was deutlich macht, dass der hier eingesetzte Lambda-Ausdruck vom Typ Comparator ist:<\/p>\n<pre>Comparator&lt;String&gt;\u00a0c\u00a0=\u00a0(String\u00a0s1,\u00a0String\u00a0s2)\u00a0-&gt;\u00a0{\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return\u00a0s1.trim().compareTo(\u00a0s2.trim()\u00a0);\u00a0}\r\n Arrays.sort(\u00a0words,\u00a0c\u00a0);<\/pre>\n<h3>Funktionale Schnittstellen<\/h3>\n<p>Nicht zu jeder Schnittstelle gibt es eine Abk\u00fcrzung \u00fcber einen Lambda-Ausdruck, und es gibt eine zentrale Bedingung, wann ein Lambda-Ausdruck verwendet werden kann.<\/p>\n<p><strong>Definition:<\/strong> Schnittstellen, die nur eine Operation (abstrakte Methode) besitzen, hei\u00dfen funktionale Schnittstellen. Ein Funktionsdeskriptor beschreibt diese Methode. Eine abstrakte Klasse mit genau einer abstrakten Methode z\u00e4hlt nicht als funktionale Schnittstelle.<\/p>\n<p>Lambda-Ausdr\u00fccke und funktionale Schnittstellen haben eine ganz besondere Beziehung, denn ein Lambda-Ausdruck ist ein Exemplar einer solchen funktionalen Schnittstelle. Nat\u00fcrlich m\u00fcssen Typen und Ausnahmen passen. Dass funktionale Schnittstellen genau eine abstrakte Methode vorschreiben, ist eine naheliegende Einschr\u00e4nkung, denn g\u00e4be es mehrere, m\u00fcsste ein Lambda-Ausdruck ja auch mehrere Implementierungen anbieten oder irgendwie eine Methode bevorzugen und andere ausblenden.<\/p>\n<p>Wenn wir ein Objekt vom Typ einer funktionalen Schnittstelle aufbauen m\u00f6chten, k\u00f6nnen wir folglich zwei Wege einschlagen: Es l\u00e4sst sich die traditionelle Konstruktion \u00fcber die Bildung von Klassen w\u00e4hlen, die funktionale Schnittstellen implementieren, und dann mit new ein Exemplar bilden, oder es l\u00e4sst sich mit kompakten Lambda-Ausdr\u00fccken arbeiten. Moderne IDEs zeigen uns an, wenn kompakte Lambda-Ausdr\u00fccke zum Beispiel statt innerer anonymer Klassen genutzt werden k\u00f6nnen, und bieten uns m\u00f6gliche Refactorings an. Lambda-Ausdr\u00fccke machen den Code kompakter und nach kurzer Eingew\u00f6hnung auch lesbarer.<\/p>\n<p><strong>Hinweis:<\/strong> Funktionale Schnittstellen m\u00fcssen auf genau eine zu implementierende Methode hinauslaufen, auch wenn aus Oberschnittstellen mehrere Operationen vorgeschrieben werden, die sich aber durch den Einsatz von Generics auf eine Operation verdichten:<\/p>\n<pre>interface\u00a0I&lt;S,T\u00a0extends\u00a0CharSequence&gt;\u00a0{\r\n \u00a0\u00a0void\u00a0len(\u00a0S\u00a0text\u00a0);\r\n \u00a0\u00a0void\u00a0len(\u00a0T\u00a0text\u00a0);\r\n }\r\n interface\u00a0FI\u00a0extends\u00a0I&lt;String,String&gt;\u00a0{\u00a0}<\/pre>\n<p>FI ist unsere funktionale Schnittstelle mit einer eindeutigen Operation len(String). Statische und Default-Methoden st\u00f6ren in funktionalen Schnittstellen nicht.<\/p>\n<h4>Viele funktionale Schnittstellen in der Java-Standardbibliothek<\/h4>\n<p>Java bringt schon viele Schnittstellen mit, die als funktionale Schnittstellen gekennzeichnet sind. Dar\u00fcber hinaus f\u00fchrt das Paket java.util.function mehr als 40 neue funktionale Schnittstellen ein. Eine kleine Auswahl:<\/p>\n<ul>\n<li>interfaceRunnable{voidrun();}<\/li>\n<li>interfaceSupplier&lt;T&gt;{Tget();}<\/li>\n<li>interfaceConsumer&lt;T&gt;{voidaccept(Tt);}<\/li>\n<li>interfaceComparator&lt;T&gt;{intcompare(To1,To2);}<\/li>\n<li>interfaceActionListener{voidactionPerformed(ActionEvente);}<\/li>\n<\/ul>\n<p>Ob die Schnittstelle noch andere Default-Methoden hat \u2013 also Schnittstellenmethoden mit vorgegebener Implementierung \u2013, ist egal, wichtig ist nur, dass sie genau eine zu implementierende Operation deklariert.<\/p>\n<h3>Typ eines Lambda-Ausdrucks ergibt sich durch Zieltyp<\/h3>\n<p>In Java hat jeder Ausdruck einen Typ. 1 und 1*2 haben einen Typ (n\u00e4mlich int), genauso wie &#8222;A&#8220; + &#8222;B&#8220; (Typ String) oder String.CASE_INSENSITIVE_ORDER (Typ Comparator&lt;String&gt;). Lambda-Ausdr\u00fccke haben auch immer einen Typ, denn ein Lambda-Ausdruck ist immer Exemplar einer funktionalen Schnittstelle. Damit steht auch der Typ fest. Allerdings ist es im Vergleich zu Ausdr\u00fccken wie 1*2 bei Lambda-Ausdr\u00fccken etwas anders gelagert, denn der Typ von Lambda-Ausdr\u00fccken ergibt sich ausschlie\u00dflich aus dem Kontext. Erinnern wir uns an den Aufruf von sort(\u2026):<\/p>\n<pre>Arrays.sort(\u00a0words,\u00a0(String\u00a0s1,\u00a0String\u00a0s2)\u00a0-&gt;\u00a0{\u00a0return\u00a0\u2026\u00a0}\u00a0);<\/pre>\n<p>Dort steht nichts vom Typ Comparator, sondern der Compiler erkennt aus dem Typ des zweiten Parameters von sort(\u2026), der ja Comparator ist, ob der Lambda-Ausdruck auf die Methode des Comparators passt oder nicht.<\/p>\n<p>Der Typ eines Lambda-Ausdrucks ist folglich abh\u00e4ngig davon, welche funktionale Schnittstelle er im jeweiligen Kontext gerade realisiert. Der Compiler kann ohne Kenntnis des Zieltyps (engl. target type) keinen Lambda-Ausdruck aufbauen.<\/p>\n<p><strong>Beispiel:<\/strong> Callable und Supplier sind funktionale Schnittstellen mit Methoden, die keine Parameterlisten deklarieren und eine Referenz zur\u00fcckgeben; der Code f\u00fcr den Lambda-Ausdruck sieht gleich aus:<\/p>\n<pre>java.util.concurrent.Callable&lt;String&gt;\u00a0c\u00a0=\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a0\"R\u00fcckgabe\";\u00a0};\r\n java.util.function.Supplier&lt;String&gt;\u00a0\u00a0\u00a0s\u00a0=\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a0\"R\u00fcckgabe\";\u00a0};<\/pre>\n<h4>Wer bestimmt den Zieltyp?<\/h4>\n<p>Gerade weil an dem Lambda-Ausdruck der Typ nicht abzulesen ist, kann er nur dort verwendet werden, wo ausreichend Typinformationen vorhanden sind. Das sind unter anderem die folgenden Stellen:<\/p>\n<ul>\n<li>Variablendeklarationen: etwa wie bei Supplier&lt;String&gt; s = () -&gt; { return &#8222;&#8220;; }<\/li>\n<li>Argumente an Methoden oder Konstruktoren: Der Parametertyp gibt alle Typinformationen. Ein Beispiel lieferte sort(\u2026).<\/li>\n<li>Methodenr\u00fcckgaben: Das k\u00f6nnte aussehen wie Comparator&lt;String&gt; trimComparator() { return (s1, s2) -&gt; { return \u2026 }; }.<\/li>\n<li>Bedingungsoperator: Der ?:-Operator liefert je nach Bedingung einen unterschiedlichen Lambda-Ausdruck. Beispiel: Supplier&lt;Double&gt; randomNegOrPos = random() &gt; 0.5 ? () -&gt; { return Math.random(); } : () -&gt; { return Math.random(); };<\/li>\n<\/ul>\n<h4>Parametertypen<\/h4>\n<p>In der Praxis ist der h\u00e4ufigste Fall, dass die Parametertypen von Methoden den Zieltyp vorgeben. Der Einsatz von Lambda-Ausdr\u00fccken \u00e4ndert ein wenig die Sichtweise auf \u00fcberladene Methoden. Unser Beispiel mit () -&gt; { return &#8222;R\u00fcckgabe&#8220;; } macht das deutlich, denn es \u201epasst\u201c auf den Zieltyp Callable&lt;String&gt; genauso wie auf Supplier&lt;String&gt;. Nehmen wir zwei \u00fcberladene Methoden run(\u2026) an:<\/p>\n<pre>class OverloadedFuntionalInterfaceMethods {\r\n\r\n\u00a0 static &lt;V&gt; void <strong>run( Callable&lt;V&gt; callable )<\/strong> { }\r\n\r\n\u00a0 static &lt;V&gt; void <strong>run( Supplier&lt;V&gt; callable )<\/strong> { }\r\n\r\n}<\/pre>\n<p>Spielen wir den Aufruf der Methoden einmal durch:<\/p>\n<pre>Callable&lt;String&gt;\u00a0c\u00a0=\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a0\"R\u00fcckgabe\";\u00a0};\r\n Supplier&lt;String&gt;\u00a0s\u00a0=\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a0\"R\u00fcckgabe\";\u00a0};\r\n run(\u00a0c\u00a0);\r\n run(\u00a0s\u00a0);\r\n \/\/\u00a0run(\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a0\"R\u00fcckgabe\";\u00a0}\u00a0);\u00a0\/\/\u00a0BANG!\u00a0Compilerfehler\r\n run(\u00a0(Callable&lt;String&gt;)\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a0\"R\u00fcckgabe\";\u00a0}\u00a0);<\/pre>\n<p>Rufen wir run(c) bzw. run(s) auf, ist das kein Problem, denn c und s sind klar typisiert. Aber run(\u2026) mit dem Lambda-Ausdruck aufzurufen funktioniert nicht, denn der Zieltyp (entweder Callable oder Supplier) ist mehrdeutig; der (Eclipse-)Compiler meldet: \u201eThe method run(Callable&lt;Object&gt;) is ambiguous for the type T\u201c. Hier sorgt eine explizite Typumwandlung f\u00fcr Abhilfe.<\/p>\n<p><strong>Tipp zum API-Design:\u00a0<\/strong>Aus Sicht eines API-Designers sind \u00fcberladene Methoden nat\u00fcrlich sch\u00f6n, aus Sicht des Nutzers sind Typumwandlungen aber nicht sch\u00f6n. Um explizite Typumwandlungen zu vermeiden, sollte auf \u00fcberladene Methoden verzichtet werden, wenn diese den Parametertyp einer funktionalen Schnittstelle aufweisen. Stattdessen lassen sich die Methoden unterschiedlich benennen (was bei Konstruktoren nat\u00fcrlich nicht funktioniert). Wird in unserem Fall die Methode runCallable(\u2026) und runSupplier(\u2026) genannt, ist keine Typumwandlung mehr n\u00f6tig, und der Compiler kann den Typ herleiten.<\/p>\n<h4>R\u00fcckgabetypen<\/h4>\n<p>Typ-Inferenz spielt bei Lambda-Ausdr\u00fccken eine gro\u00dfe Rolle \u2013 das gilt insbesondere f\u00fcr die R\u00fcckgabetypen, die \u00fcberhaupt nicht in der Deklaration auftauchen und f\u00fcr die es gar keine Syntax gibt; der Compiler \u201einferrt\u201c sie. In unserem Beispiel<\/p>\n<pre>Comparator&lt;String&gt;\u00a0c\u00a0=\r\n \u00a0\u00a0(String\u00a0s1,\u00a0String\u00a0s2)\u00a0-&gt;\u00a0{\u00a0return\u00a0s1.trim().compareTo(\u00a0s2.trim()\u00a0);\u00a0};<\/pre>\n<p>ist String als Parametertyp der Comparator-Methode ausdr\u00fccklich gegeben; der R\u00fcckgabetyp int, den der Ausdruck s1.trim().compareTo( s2.trim()) liefert, taucht dagegen nicht auf.<\/p>\n<p>Mitunter muss dem Compiler etwas geholfen werden: Nehmen wir die funktionale Schnittstelle Supplier&lt;T&gt;, die eine Methode T get() deklariert, f\u00fcr ein Beispiel. Die Zuweisung<\/p>\n<pre>Supplier&lt;Long&gt;\u00a0two\u00a0\u00a0=\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a02;\u00a0}\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0N\u00a0Compilerfehler<\/pre>\n<p>ist nicht korrekt und f\u00fchrt zum Compilerfehler \u201eincompatible types: bad return type in lambda expression\u201c. 2 ist ein Literal vom Typ int, und der Compiler kann es nicht an Long anpassen. Wir m\u00fcssen schreiben<\/p>\n<pre>Supplier&lt;Long&gt;\u00a0two\u00a0\u00a0=\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a02L\u00a0};<\/pre>\n<p>oder<\/p>\n<pre>Supplier&lt;Long&gt;\u00a0two\u00a0\u00a0=\u00a0()\u00a0-&gt;\u00a0{\u00a0return\u00a0(long)\u00a02\u00a0};<\/pre>\n<p>Bei Lambda-Ausdr\u00fccken gelten keine wirklich neuen Regeln im Vergleich zu Methodenr\u00fcckgaben, denn auch eine Methodendeklaration wie<\/p>\n<pre>Long\u00a0two()\u00a0{\u00a0return\u00a02;\u00a0}\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0BANG!\u00a0Compilerfehler<\/pre>\n<p>wird vom Compiler bem\u00e4ngelt. Doch weil Wrapper-Typen durch die Generics bei funktionalen Schnittstellen viel h\u00e4ufiger sind, treten diese Besonderheiten \u00f6fter auf als bei Methodendeklarationen.<\/p>\n<h4>Sind Lambda-Ausdr\u00fccke Objekte?<\/h4>\n<p>Ein Lambda-Ausdruck ist ein Exemplar einer funktionalen Schnittstelle und tritt als Objekt auf. Bei Objekten besteht normalerweise zu java.lang.Object immer eine nat\u00fcrliche Ist-eine-Art-von-Beziehung. Fehlt aber der Kontext, ist selbst die Ist-eine-Art-von-Beziehung zu java.lang.Object gest\u00f6rt und Folgendes nicht korrekt:<\/p>\n<pre>Object\u00a0o\u00a0=\u00a0()\u00a0-&gt;\u00a0{};\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0BANG!\u00a0Compilerfehler<\/pre>\n<p>Der Compilerfehler ist: \u201eincompatible types: the target type must be a functional interface\u201c. Nur eine explizite Typumwandlung kann den Fehler korrigieren und dem Compiler den Zieltyp vorgeben:<\/p>\n<pre>Object\u00a0r\u00a0=\u00a0(Runnable)\u00a0()\u00a0-&gt;\u00a0{};<\/pre>\n<p>Lambda-Ausdr\u00fccke haben keinen eigenen Typ an sich, und f\u00fcr das Typsystem von Java \u00e4ndert sich im Prinzip nichts. M\u00f6glicherweise \u00e4ndert sich das in sp\u00e4teren Java-Versionen.<\/p>\n<p><strong>Hinweis:<\/strong> Dass Lambda-Ausdr\u00fccke Objekte sind, ist eine Eigenschaft, die nicht \u00fcberstrapaziert werden sollte. So sind die \u00fcblichen Object-Methoden equals(Object), hashCode(), getClass(), toString() und die zur Thread-Kontrolle ohne besondere Bedeutung. Es sollte auch nie ein Szenario geben, in dem Lambda-Ausdr\u00fccke mit == verglichen werden m\u00fcssen, denn das Ergebnis ist laut Spezifikation undefiniert. Echte Objekte haben eine Identit\u00e4t, einen Identity-Hashcode, lassen sich vergleichen und mit instanceof testen, k\u00f6nnen mit einem synchronisierten Block abgesichert werden; all dies gilt f\u00fcr Lambda-Ausdr\u00fccke nicht. Im Grunde charakterisiert der Begriff \u201eLambda-Ausdruck\u201c schon sehr gut, was wir nie vergessen sollten: Es handelt sich um einen Ausdruck, also etwas, was ausgewertet wird und ein Ergebnis produziert.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In unserem Beispiel haben wir den Lambda-Ausdruck als Argument von Array.sort(\u2026) eingesetzt: Arrays.sort(\u00a0words, \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(String\u00a0s1,\u00a0String\u00a0s2)\u00a0-&gt;\u00a0{\u00a0return\u00a0s1.trim().compareTo(s2.trim());\u00a0}\u00a0); Wir h\u00e4tten aber auch den Lambda-Ausdruck explizit einer lokalen Variablen zuweisen k\u00f6nnen, was deutlich macht, dass der hier eingesetzte Lambda-Ausdruck vom Typ Comparator ist: Comparator&lt;String&gt;\u00a0c\u00a0=\u00a0(String\u00a0s1,\u00a0String\u00a0s2)\u00a0-&gt;\u00a0{ \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return\u00a0s1.trim().compareTo(\u00a0s2.trim()\u00a0);\u00a0} Arrays.sort(\u00a0words,\u00a0c\u00a0); Funktionale Schnittstellen Nicht zu jeder Schnittstelle gibt es eine Abk\u00fcrzung \u00fcber einen Lambda-Ausdruck, und [&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-1632","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\/1632","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=1632"}],"version-history":[{"count":3,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1632\/revisions"}],"predecessor-version":[{"id":3961,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1632\/revisions\/3961"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=1632"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=1632"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=1632"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}