Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | NEWSLETTER | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
NEWSLETTER 
CONTACT 
Effective Java - Java 8 - Functional Programming in Java

Effective Java - Java 8 - Functional Programming in Java  
Java 8
Funktionale Programmierung in Java
 

Java Magazin, September 2013
Klaus Kreft & Angelika Langer

Dies ist die Überarbeitung eines Manuskripts für einen Artikel, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im Java Magazin erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

 

Im April 2013 hat Oracle die Java Community darüber informiert, dass der Java-8-Releasetermin von September 2013 auf März 2014 verschoben wird.  Diese Verschiebung gibt uns Zeit, uns auf die neuen Sprachmittel und die neuen JDK-Abstraktionen von Java 8 in Ruhe vorzubereiten. Mit diesem Beitrag beginnen wir deshalb eine Serie von Beiträgen zu den Neuerungen in Java 8.  Unter anderem wird es neue Sprachelemente geben, die einen eher funktionalen Programmierstil in Java unterstützen werden.  Dabei geht es um die sogenannten Lambda-Ausdrücke.  Ehe wir uns jedoch die Lambda-Ausdrücke in einem der nachfolgenden Beiträge genauer ansehen, wollen wir uns zunächst damit befassen, was funktionale Programmierung generell ausmacht, wo man funktionale Ansätze praktisch anwenden kann und wie funktionale Programmierung konkret in Java aussehen wird.

Objekt-Orientierte vs. Funktionale Programmierung

In objekt-orientierten Programmiersprachen (wie zum Beispiel Java) spielen Objekte eine wesentliche Rolle.  Als Java-Entwickler beschreiben wir, wie Objekte aussehen, wenn wir eine Klasse definieren, d.h. wir legen fest, welche Daten den Zustand eines Objekts beschreiben und welche Methoden die Fähigkeiten eines Objekts ausmachen.  Wir erzeugen Objekte, wenn wir von den Klassen Instanzen bilden.  Wir verändern Objekte, z.B. wenn wir die Felder ändern oder Methoden aufrufen, die dies tun.  Wir reichen Objekte herum, z.B. wenn wir sie als Argumente an Methoden übergeben.  Mit Objekten sind wir als Java-Entwickler bestens vertraut.

 
 

In funktionalen Sprachen (wie zum Beispiel Erlang, Haskell, ...) stehen nicht Objekte, sondern Funktionen im Vordergrund.  Funktionen ähneln Methoden; sie repräsentieren ausführbare Funktionalität.  Sowohl Methoden als auch Funktionen werden aufgerufen und ausgeführt.  Funktionen in funktionalen Sprachen werden aber darüber hinaus herumgereicht.  Man übergibt sie beispielsweise als Argumente an Operationen; diese Operationen können dann die übergebenen Funktionen in einem bestimmten Kontext aufrufen.  Funktionen können auch als Returnwert einer Operation zurückgegeben werden.  Das heißt, in funktionalen Sprachen werden Funktionen herumgereicht, wie Objekte in objekt-orientierten Sprachen.  Dieses Prinzip des Herumreichens von Funktionen wird auch als "code-as-data" bezeichnet. 
 
 

Funktionen werden aber nicht nur übergeben und aufgerufen, sondern auch kombiniert und verkettet oder manipuliert und verändert.  Es gibt z.B. das sogenannte Currying ( benannt nach Haskell Brooks Curry), bei dem aus einer Funktion mit mehreren Argumenten durch Argument-Binding eine Funktion mit einem Argument gemacht wird.  In einer reinen funktionalen Sprache ("pure functional language") haben die Funktionen nicht einmal Seiteneffekte.  Das heißt insbesondere, dass Funktionen keine Daten verändern, sondern bestenfalls neue Daten erzeugen.
 
 

Soviel zur Theorie.  Was fängt man damit in der Praxis an?  Kann man funktionale Prinzipien in Java überhaupt gebrauchen? Zur Illustration wollen wir uns ein Idiom ansehen, bei dem Funktionen eine wesentliche Rolle spielen und das auch in Java recht nützlich sein kann.  Es geht um das Execute-Around-Method-Pattern .

Das Execute-Around-Method-Pattern

Bei dem Execute-Around-Method-Pattern (siehe / EAM1 /, / EAM2 /) geht es darum, strukturell ähnlichen Code so zu zerlegen, dass die immer wiederkehrende, identische Struktur heraus gelöst und in eine Hilfsmethode ausgelagert wird.  Der Teil, der in dieser Struktur variiert, wird an die Hilfsmethode übergeben und in dieser Hilfsmethode eingebettet in die Struktur an der richtigen Stelle aufgerufen.  Beispiele dafür solche wiederkehrenden Strukturen gibt es viele:

 
 

Verwendung von Ressourcen : Wenn eine Ressource verwendet wird, dann ergibt sich oft eine wiederkehrende Struktur, nämlich

acquire resource

use resource

release resource
 
 

Das Anfordern und Freigeben der Ressource ist oft identisch, aber die Benutzung dazwischen variiert. Ein Beispiel für solche Ressourcen sind die expliziten Locks wie zum Beispiel das  ReentrantLock .  Hier ist die immer gleiche Struktur, die sich bei der Benutzung von expliziten Locks ergibt:
 
 

              lock.lock();

              try {

                             ... critical region ...

              } finally {

                            lock.unlock();

              }
 
 

Das Anfordern und Freigeben des Locks ist immer gleich, nur die Anweisungen dazwischen variieren. 
 
 

Exception Handling : Wenn Operationen aus einem bestimmten Framework verwendet werden, dann werfen sie oft die gleichen Exceptions, die immer gleich behandelt werden.
 
 

              try {

                   ...  invoke operations ...

              } catch (ExceptionType_1 e1) { ... }

                catch (ExceptionType_2 e2) { ... }

                catch (ExceptionType_3 e3) { ... }
 
 

Die  catch -Klauseln sind immer gleich, aber die im  try -Block aufgerufenen Operationen sind unterschiedlich.
 
 

Iterierung : Es wird ein Iterator angefordert und aufs jeweils nächste Element in einer Sequenz weitergeschaltet, bis das letzte Element erreicht ist.
 
 

              Iterator iter = seq.iterator();

              while (iter.hasNext()) {

                            Object elem = iter.next();

                             ... use element ...

              }
 
 

Die Handhabung des Iterators ist immer gleich; lediglich die Verwendung des jeweiligen Elements variiert.
 
 

Beim Execute-Around-Method-Pattern wird der gemeinsame, wiederkehrende Teil in eine Hilfsmethode ausgelagert. Der veränderliche Teil wird als Argument an die Hilfsmethode übergeben. Wir wollen es einmal am Beispiel der Iteration demonstrieren.
 
 

Wir definieren eine Hilfsmethode  forEach :
 
 

              public class Utilities {

                 public static <E> void forEach(Iterable<E> seq, Consumer<E> block) {

                     Iterator<E> iter = seq.iterator();

                     while (iter.hasNext()) {

                            E elem = iter.next();

                            block.accept (elem);

                     }

                 }

              }
 
 

Die Hilfsmethode  forEach enthält den strukturell wiederkehrenden Teil, nämlich das Anfordern des Iterators, die Abfrage auf das Ende der Sequenz und das Weiterschalten des Iterators auf das jeweils nächste Element.  Die Verwendung des Elements ist der sich unterscheidende Teil.  Er wird von Außen an die Hilfsmethode übergeben und in der Hilfsmethode an entsprechender Stelle aufgerufen.  Die Hilfsmethode  forEach bekommt deshalb als Argumente die Sequenz der Elemente, auf der man iterieren will, und die Funktionalität, die während der Iterierung auf jedes Element in der Sequenz angewandt werden soll. 
 
 

Für die Beschreibung der Funktionalität, die auf jedes Element angewandt wird, definieren wir ein Interface  Consumer :
 
 

              public interface Consumer<T> {

                            void accept(T t);

              }
 
 

Dieses Interface gibt es tatsächlich in Java 8 im Package  java.util.function .
 
 

Mit dieser Zerlegung in die Hilfsmethode mit dem strukturell identischen Teil und das Interface mit dem variierenden Teil brauchen wir die Iterierung nicht mehr redundant hinschreiben.  Hier ist ein Benutzungsbeispiel.  Wir wollen alle Elemente aus einer Liste von Zahlen ausgeben.
 
 

Herkömmlich sieht es so aus:
 
 

              List<Integer> numbers = new ArrayList<>();

              ... populate list ...

              Iterator iter = numbers.iterator();

              while (iter.hasNext()) {

                            Integer elem = iter.next();

                             System.out.println(elem);

              }
 
 

Gemäß Execute-Around-Method-Pattern sieht es so aus:
 
 

              List<Integer> numbers = new ArrayList<>();

              ... populate list ...

              Utilities.forEach(numbers,

                                new Consumer<Integer>() {

                                           public void accept(Integer elem) {

                                                   System.out.println(elem);

                                           }

                                });
 
 

Nun mag man sich fragen, was an der Execute-Around-Method-Version besser sein soll als an der guten alten Iterierung per Schleife.  Mit klassischen Java-Mitteln, so wie sie uns in Java 7 zur Verfügung stehen, ist nichts gewonnen.  Man muss eine Implementierung des  Consumer -Interfaces definieren, um den Consumer an die Hilfsmethode  forEach zu übergeben, und das ist selbst unter Verwendung von anonymen inneren Klassen noch recht umständlich.
 
 

Genau diese syntaktische Umständlichkeit wird in Java 8 mit den Lambda-Ausdrücken verschwinden (siehe / LAM /, / TUT /).
 
 

In Java 8 mit einem Lambda-Ausdruck sieht es viel eleganter aus:
 
 

              List<Integer> numbers = new ArrayList<>();

              ... populate list ...

              Utilities.forEach(numbers,  e -> System.out.println(e ) );
 
 

Es geht auch noch eleganter mit Hilfe von Methoden-Referenzen - einem weiteren neuen Sprachmittel in Java 8.
 
 

So sieht es dann in Java 8 mit einer Methoden-Referenz aus:
 
 

              List<Integer> numbers = new ArrayList<>();

              ... populate list ...

              Utilities.forEach(numbers,  System :: println );
 
 

Auf die Syntax von Lambda-Ausdrücken wie  e -> System.out.println(e) und Methoden-Referenzen wie  System .out ::println wollen wir in diesem Beitrag nicht näher eingehen.  Das besprechen wir im nächsten Artikel der Serie im Detail.  Aber auch ohne große Erläuterung kann man intuitiv verstehen, dass der Lambda-Ausdruck so etwas Ähnliches wie eine Funktion ist.  Er nimmt ein Argument mit Namen  e , dessen Typ sich der Compiler selbst überlegen kann.  Augenscheinlich soll es ein  Integer aus der Liste sein.  Dieses Argument  e wird per  System.out.println -Methode ausgegeben.  Auf diese Weise wird die  forEach -Methode alle Zahlen in der Liste  numbers mit  println auf  System.out ausgeben.
 
 

Die Methoden-Referenz ist auch selbsterklärend.   System .out ::println ist die  println -Methode von  System.out .  In der  forEach -Methode sollen also alle Elemente mit  println nach  System.out ausgegeben werden.
 
 
 
 

Im JDK 8 wird es solche Hilfsmethoden wie  forEach geben. Sie sind dann aber nicht in irgendwelchen Utility-Klassen definiert, sondern die Collections selbst sind erweitert worden. In Java 8 hat jede Collection aus dem  java.util Package eine  forEach -Methode, und zwar erbt sie diese von ihrem Super-Interface  Iterable .   Das  Iterable -Interface ist für Java 8 erweitert worden und sieht in Java 8 so aus:
 
 

public interface Iterable<T> {

    Iterator<T> iterator();
 
 

    default void forEach(Consumer<? super T> action) {

        for (T t : this) {

            action.accept(t);

        }

    }

}
 
 

Das  Iterable -Interface hat zusätzlich zur  iterator -Methode, die es schon immer hatte, eine  forEach -Methode bekommen.
 
 

Um die existierenden Interfaces im JDK so wie oben gezeigt erweitern zu können, hat man mit Java 8 die sogenannten Default-Methoden erfunden. Darauf werden wir in einem der Folgebeiträge genauer eingehen.  Hier nur ganz kurz: Normalerweise kann man ein Interface nicht problemlos erweitern.  Wenn man Methoden hinzufügt, dann müssen alle abgeleiteten Klassen diese Methode implementieren.  Andernfalls gibt es Fehlermeldungen bei der Compilierung.  Die Default-Methoden sind nun Methoden, die eine Implementierung haben.  Das heißt, sie sind nicht abstrakt und müssen von den abgeleiteten Klassen auch nicht implementiert werden.  Alle Klassen, die keine Implementierung für die neue zusätzliche Methode haben, erben einfach die Default-Implementierung aus dem Interface.  Auf diese Weise kann man existierende Interfaces erweitern, ohne die abgeleiteten Klassen ändern zu müssen.  Die  forEach -Methode im  Iterable -Interface ist eine solche Default-Methode.  Sie hat eine Implementierung. Sie verwendet in der Implementierung die for-each-Schleife, die es seit Java 5 gibt und die intern einen Iterator verwendet.  Die  forEach -Methode im Interface  Iterable entspricht der  forEach -Methode aus unserer  Utilities -Klasse, mit dem kleinen Unterschied, dass sie das erste Argument nicht braucht, weil sie als nicht-statische Methode der Collection ohnehin über die  this -Referenz auf die Collection zugreifen kann.
 
 

Das Beispiel von oben sieht in Java 8 letztendlich so aus:
 
 

              List<Integer> numbers = new ArrayList<>();

              ... populate list ...

              numbers.forEach( System .out ::println );
 
 
 
 

Die herkömmliche Art der Iterierung mit einem expliziten Iterator bezeichnet man im Übrigen als externe Iterierung , im Gegensatz zur internen Iterierung in der  forEach -Methode einer Collection (siehe / GOF /).  Bei der externen Iterierung wird der Iterator an den externen Benutzer einer Collection gegeben und der Benutzer bestimmt, wie der den Iterator verwendet, um alle Elemente der Sequenz zu besuchen.  Bei der internen Iterierung bestimmt die Collection selbst, wie sie in ihrer  forEach -Methode alle Elemente besucht.  Das kann sie mit einem Iterator machen, so wie wir es im Beispiel gesehen haben.  Sie kann es aber auch ganz anders machen, zum Beispiel parallel mit vielen Threads statt sequentiell mit nur einem Thread.
 
 

Genau die parallele Ausführung von Operationen wie  forEach sind der wesentliche Grund dafür, dass man die Sprache um Lambda-Ausdrücke erweitert hat.  Eines der Ziele in Java 8 ist die bessere Unterstützung von Parallelverarbeitung.  Deshalb wird es neue Abstraktionen im JDK-Collection-Framework geben, nämlich sogenannte Streams .  Diese Streams haben Operationen wie  forEach (oder auch  sort filter , etc.) mit interner Iterierung, die wahlweise sequentiell oder parallel ausgeführt werden können.  Die Streams und ihr umfangreiches API werden wir uns in einem der nachfolgenden Beiträge im Detail ansehen.

Funktionale Programmierung in Java

In dem oben geschilderten Beispiel der internen Iterierung bzw. des Execute-Around-Method-Patterns sieht man typische Elemente der funktionalen Programmierung.  Beispielsweise sieht man das "code-as-data"-Prinzip: die  forEach -Methode benötigt als Argument eine Funktion, die auf alle Elemente der Collection angewandt werden soll.  Es wird zwar streng genommen ein Objekt als Argument übergeben, aber dieses Objekt repräsentiert Funktionalität. Das einzige, was an dem Objekt interessant ist, ist die eine Methode, die es mitbringt und die auf alle Elemente der Sequenz angewandt werden soll.  In diesem Sinne ist das  Consumer -Argument der  forEach -Methode eine Funktion. In Java 7 muss dafür umständlich eine anonyme innere Klasse definiert werden.  In Java 8 mit den Lambda-Ausdrücken und Methoden-Referenzen sieht die Funktionalität optisch und syntaktisch so aus, wie man sich eine Funktion vorstellt

 
 
Allerdings gehen die Spracherweiterung in Java 8 nicht so weit, dass aus Java nun eine funktionale Sprache wird.  Viele Dinge, die es in funktionalen Sprachen gibt, wird es in Java nicht geben. *) Beispielsweise hat Java keine Funktionstypen.  Die Sprach-Designer haben es bewusst vermieden, das Typsystem von Java umzukrempeln und um eine völlig neue Kategorie von Typ zu erweitern.  Stattdessen hat man sich überlegt, wie der Compiler es bewerkstelligen kann, als Typ für Lambda-Ausdrücke und Methoden-Referenzen ganz "normale" Typen (d.h. Klassen oder Interfaces) zu verwenden.
 
*) genauer gesagt:  es wird sie zunächst einmal nicht geben. Es ist nicht ausgeschlossen, dass es in Java 9, 10 oder später weitergehende Unterstützung für funktionale Programmierung in Java geben wird.  Die Sprach-Designer haben darauf geachtet, dass der Weg für zukünftige Erweiterungen nicht verbaut wird. Aber für Java 8 wurde versucht, die Spracherweiterung erst einmal minimalistisch zu gestalten und nur das hinzuzufügen, was für die Weiterentwicklung des JDK zwingend erforderlich ist.

 

Target Typing und SAM Types

Sehen wir uns die Einbettung von Lambda-Ausdrücken und Methoden-Referenzen ins Java-Typsystem anhand unseres Beispiels an.

 
 

Noch einmal das Beispiel von oben:
 
 

              List<Integer> numbers = new ArrayList<>();

              ... populate list ...

              numbers.forEach( System.out::println );
 
 

Der Compiler geht prinzipiell so vor:  er schaut sich den Kontext an, in dem ein Lambda-Ausdruck oder eine Methoden-Referenz steht, überlegt, welcher Typ von Objekt an dieser Stelle benötigt wird, und deduziert daraus den Typ für den Lambda-Ausdruck oder die Methoden-Referenz. 
 
 

In unserem Beispiel findet er die Methoden-Referenz  System.out::println als Argument im Aufruf der  forEach -Methode.  Der Compiler schaut sich also den deklarierten Argumenttyp der  forEach -Methode an.  Weil es die  forEach -Methode einer  List<Integer> ist, stellt der Compiler fest, dass für den Methodenaufruf ein Objekt vom Typ  Consumer<Integer> benötigt wird.   Consumer<Integer> ist ein Interface mit einer einzigen abstrakten Methoden, nämlich der  accept -Methode.  Nun prüft der Compiler, ob die Signatur der  accept -Methode kompatibel zur Methoden-Referenz  System.out::println ist.  Die  accept -Methode von  Consumer<Integer > nimmt ein Argument vom Typ  Integer , gibt void zurück und wirft keine checked-Exceptions.  Das passt zu unserer Methoden-Referenz  System.out::println .  Die  println -Methode ist überladen und unter all den vielen  println -Varianten gibt es eine, die ein Argument vom Typ  Integer nimmt,  void zurück gibt und keine checked-Exceptions wirft.  Das heißt, die  accept -Methode aus dem  Consumer<Integer> -Interface hat dieselbe Signatur wie die Methoden-Referenz  System.out::println .  Der Compiler schließt daraus, dass die -Referenz  System.out::println in diesem Kontext vom Typ  Consumer<Integer> ist. 
 
 

Diesen Prozess der Deduktion des Typs eines Lambda-Ausdrucks oder einer Methoden-Referenz wird als Target Typing bezeichnet, weil dabei der Zieltyp (Target Type) für den Ausdruck oder die Referenz aus dem Kontext ermittelt wird. Für Lambda-Ausdrücke funktioniert das Target Typing ganz analog.
 
 

Interfaces wie  Consumer mit einer einzigen abstrakten Methode heißen übrigens Functional Interface Types (oder auch SAM Types , wobei SAM für Single Abstract Method steht).  Die SAM-Typen spielen beim Target Typing eine wesentliche Rolle.  Sie sind nämlich die einzigen Typen, die als Zieltypen in Frage kommen.
 
 

Über diesen Trick mit den SAM-Typen und der Deduktion eines kontext-abhängigen Zieltyps konnte es vermieden werden, gravierend in das Typsystem von Java einzugreifen.  Deshalb gibt es in Java - anders als in funktionalen Sprachen - keine spezielle Kategorie von Typen, mit denen man Funktionen oder Funktions-Signaturen beschreiben könnte. 

Seiteneffekte

Das Fehlen von echten Funktionstypen ist aber nur eine Eigenart, die funktionale Programmierung in Java von funktionalen Sprachen unterscheidet.  In reinen funktionalen Sprachen sind die Funktionen stets frei von Seiteneffekten.  Insbesondere modifiziert eine reine Funktion keine Daten, sondern produziert ein Ergebnis.  Das ist in Java natürlich anders.  Es gibt in Java gar keine Möglichkeit, eine Funktion daran zu hindern, Felder oder Variablen zu modifizieren. 

 
 

In unserem Beispiel haben unsere Lambda-Ausdrücke und Methoden-Referenzen zwar nichts modifiziert, aber einen Seiteneffekt, nämlich die Ausgabe auf  System.out , haben sie dennoch produziert.  Für diese Funktionen macht es einen Unterschied, ob sie  mehrfach aufgerufen werden oder in welche Reihenfolge sie aufgerufen werden, denn es hat Einfluss auf die Ausgabe.  Bei einer reinen Funktion, die keinerlei Seiteneffekte hat, wäre es völlig egal, wie oft und in welcher Reihenfolge sie ausgeführt wird.  Eine reine Funktion wäre beispielsweise folgender Lambda-Ausdruck:
 
 

              IntPredicate isEven = (int i) -> { return i%2==0; };
 
 

Dabei ist  IntPredicate ein SAM-Typ aus dem Package  java.util.function mit einer einzigen abstrakte Methode, die so aussieht:   boolean test(int value) .
 
 

Dieser Lambda-Ausdruck  (int i) -> { return i%2==0; }  nimmt einen  int -Wert und liefert  true zurück, wenn es eine gerade Zahl ist, und andernfalls  false .  Hier wird überhaupt kein Seiteneffekt ausgelöst.  Es wird einfach nur ein Wert genommen und ein Boolesches Ergebnis zurück geliefert.  Diese Funktion kann aufrufen werden, so oft man will und in jeder beliebigen Reihenfolge.  Es macht überhaupt keinen Unterschied. 
 
 

Hier zum Kontrast ein Lambda-Ausdruck, der Modifikationen macht:
 
 

              List<Point> points = new ArrayList<>();

              ... populate list ...

              points.forEach(p -> { p.x = 0; });
 
 

Hier werden alle Elemente der Sequenz modifiziert; es sind  Point -Objekte, deren x-Koordinate in dem Lambda-Ausdruck geändert wird.  Das ist im Sinne der funktionalen Programmierung schlechter Stil, kann aber in Java nicht verhindert werden.  Wenn das Argument einer Funktion eine Referenz auf ein veränderliches Objekt ist, dann kann die Funktion Modifikationen machen.  Java hat einfach keine Sprachmittel, um solche Modifikationen zu verhindern.
 
 

Solche Lambda-Ausdrücke sind u.U. problematisch.  Wir werden in nachfolgenden Beiträgen erläutern warum.  Aber bereits hier sollte schon klar sein, dass man mit modifizierenden Lambda-Ausdrücken leicht Fehler machen kann.   Hier ist eine solche Fehlersituation:
 
 

              List<Point> points = new ArrayList<>();

              ... populate list ...

              points.forEach(p -> points.add(new Point(0,p.y)));
 
 

In dem Lambda-Ausdruck werden während der Iterierung neue Elemente in die Collection eingefügt. Das scheitert zur Laufzeit mit einer  ConcurrentModificationException .

Zusammenfassung und Ausblick

Java 8 wird neue Sprachmittel haben, die in gewissem Umfang funktionale Programmierung in Java unterstützen.  Die betreffenden Sprachmittel sind Lambda-Ausdrücke und Methoden-Referenzen.  Wir haben uns in diesem Beitrag das Execute-Around-Method-Pattern sowie die Interne Iterierung als Spezialfall davon angesehen.  Beides sind Idiome, die mit den neuen Sprachmitteln profitieren.  Mit Lambda-Ausdrücken und Methoden-Referenzen sind sie wesentlich einfacher zu benutzen.  In Java 8 werden die Collections interne Iterierung unterstützen.  Genau für diese Erweiterungen der Collections im JDK sind die neuen Sprachmittel entwickelt worden.
Im nächsten Beitrag sehen wir und die Lambda-Ausdrücke und Methoden-Referenzen genauer an.

 
 

Literaturverweise

/EAM1/ Frank Buschmann, Kevlin Henney, Douglas C. Schmidt
Pattern-Oriented Software Architecture, Wiley, 2007
/EAM2/ Linda Rising
The Pattern Almanac, Addison-Wesley, 2000
/GOF/ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
Design Patterns, Addison-Wesley, 1994 
/LAM/ Project Lambda
URL: http://openjdk.java.net/projects/lambda/
/TUT/ Angelika Langer, Klaus Kreft
Lambda Tutorial
URL: http://www.AngelikaLanger.com/Lambdas/Lambdas.html

DDie gesamte  Serie über Java 8:

/JAV8-0/ Neue Features in Java 8 - Überblick
Klaus Kreft & Angelika Langer, Java Magazin, März 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/73.Java8.Overview/73.Java8.Overview.html
/JAV8-1/ Funktionale Programmierung in Java
Klaus Kreft & Angelika Langer, Java Magazin, September 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/70.Java8.FunctionalProg/70.Java8.FunctionalProg.html
/JAV8-2/ Lambda-Ausdrücke und Methoden-Referenzen
Klaus Kreft & Angelika Langer, Java Magazin, Dezember 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/71.Java8.Lambdas/71.Java8.Lambdas.html
/JAV8-3/ Default-Methoden und statische Methoden in Interfaces
Klaus Kreft & Angelika Langer, Java Magazin, Februar 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/72.Java8.DefaultMethods/72.Java8.DefaultMethods.html
/JAV8-4/ Übersicht über das Stream API in Java 8
Klaus Kreft & Angelika Langer, Java Magazin, Mai 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/74.Java8.Streams-Overview/74.Java8.Streams-Overview.html
/JAV8-5/ Stream-Erzeugung und Stream-Operationen
Klaus Kreft & Angelika Langer, Java Magazin, Juli 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/75.Java8.Fundamental-Stream-Operations/75.Java8.Fundamental-Stream-Operations.html
/JAV8-6/ Stream-Kollektoren und die Stream-Operation collect()
Klaus Kreft & Angelika Langer, Java Magazin, September 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/76.Java8.Stream-Collectors/76.Java8.Stream-Collectors.html
/JAV8-7/ Stateful Lambdas - Regeln für die Seiteneffekte in Lambda-Ausdrücken, die an Stream-Operationen übergeben werden
Klaus Kreft & Angelika Langer, Java Magazin, November 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/77.Java8.Streams-and-Statefulness/77.Java8.Streams-and-Statefulness.html
/JAV8-8/ Das Date/Time API
Klaus Kreft & Angelika Langer, Java Magazin, Januar 2015
URL: http://www.angelikalanger.com/Articles/EffectiveJava/78.Java8.Date-Time-API/78.Java8.Date-Time-API.html
/JAV8-9/ CompletableFuture
Klaus Kreft & Angelika Langer, Java Magazin, März 2015
URL: http://www.angelikalanger.com/Articles/EffectiveJava/79.Java8.CompletableFuture/79.Java8.CompletableFuture.html
/JAV8-10/ Optional<T>
Klaus Kreft & Angelika Langer, Java Magazin, Mai 2015
URL: http://www.angelikalanger.com/Articles/EffectiveJava/80.Java8.Optional-Result/80.Java8.Optional-Result.html

 
 

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
Lambdas & Streams - Java 8 Language Features and Stream API & Internals
3 day seminar ( open enrollment and on-site)
Java 8 - Lambdas & Stream, New Concurrency Utilities, Date/Time API
4 day seminar ( open enrollment and on-site)
Effective Java - Advanced Java Programming Idioms 
4 day seminar ( open enrollment and on-site)
 
Related Reading
Lambda & Streams Tutorial & Reference
In-Depth Coverage of all aspects of lambdas & streams
Lambdas in Java 8
Conference Presentation at JFokus 2012 (slides)
Lambdas in Java 8
Conference Presentation at JavaZone 2012 (video)
 

 
  © Copyright 1995-2016 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/70.Java8.FunctionalProg/70.Java8.FunctionalProg.html  last update: 2 Aug 2016