Funktionale Programmierung¶
Angenommen, wir haben eine Liste aus Zahlen (Integer) und wollen jede einzelne Zahl aus dieser Liste auf die Konsole ausgeben. Unser bisheriger Ansatz sieht ungefähr so aus:
Nun betrachten wir eine andere Methode und nutzen dazu das Interface Stream:
Mehrere Sachen sind neu:
- Aufruf der Methode
stream()für dieCollection.numbersist vom TypList.Listist ein Interface, das vom InterfaceCollectionerbt. Die Methodestream()gibt einenStreamzurück. - Ein
Streamist ein sogenannter Spliterator und vereinigt zwei Konzepte:- Ein
Spliteratorist einerseits einIteratorund ermöglicht elementweisen Zugriff (hasNext()undnext--> sieheforEach()) - 2.b und es kann den Stream in verschiedene Teile splitten (siehe z.B.
filter()).
- Ein
- Die Methode
printlnwird anders aufgerufen.- Es fehlen einerseits die Klammern
(und)beim Aufruf. - Anderseits wird die Methode nicht über die Punktschreibweise, sondern mit der neuen Syntax
::aufgerufen. Dabei handelt es sich um eine sogenannte Methodenreferenz. Tatsächlich handelt es sich gar nicht um den Aufruf der Methode, aber dazu kommen wir später genauer.
- Es fehlen einerseits die Klammern
Sie können sich den obigen Code wie folgt erklären: Die Liste wird in einen Stream umgewandelt. Mithilfe von forEach() wird jedes einzelne Element aus der Liste betrachtet und an die println()-Methode gesendet. Diese gibt jedes einzelne Element auf die Konsole aus.
Lambda-Ausdrücke¶
Im obigen Beispiel haben wir println über die Methodenreferenz "aufgerufen". Diese Methode erwartet keinen Parameter. Wir erweitern unser Beispiel zunächst und wollen nur die geraden Zahlen aus der numbers-Liste ausgeben lassen. Dazu schreiben wir uns zunächst folgende Methode:
Mithilfe dieser Methode filtern wir nun zunächst den Stream (wir splitten den Stream in gerade und ungerade Zahlen und lassen nur die geraden Zahlen "durch"). Dazu nutzen wir die Methode filter() (siehe Klasse Stream):
Als Parameter übergeben wir der filter()-Methode die Methodenreferenz isEven (siehe oben) - diese haben wir in der Klasse Functional01 implementiert. Diese Methode erwartet einen Parameter und "irgendwie" werden jetzt die einzelnen Zahlen aus dem Stream an die isEven()-Methode übergeben. Das darumterliegende Prinzip schauen wir uns nun genauer an und verwenden dafür einen sogannten Lambda-Ausdruck.
Ein Lambda-Ausdruck mit einem Parameter hat die Form:
parameter -> expressionEin Lambda-Ausdruck mit mehreren Parametern hat die Form:(parameter1, parameter2) -> expression
In obiger Form gibt die expression implizit einen Wert zurück (typischer Weise ein boolean, je nach Ausdruck). Für komplexere Ausführungen kann auch ein Anweisungsblock definiert werden. Wenn aus diesem Anweisungsblock ein Wert zurückgegeben werden soll, muss darin explizit ein return angegeben werden:
Ein Lambda-Ausdruck mit komplexerem Code:
(parameter1, parameter2) -> { code }
Wir ersetzen nun die Methodenreferenz auf isEven in filter() und geben stattdessen direkt einen Lambda-Ausdruck an:
Den Parameter haben wir hier number genannt. Wir können ihn im Ausdruck frei wählen und müssen ihn nicht deklarieren. Hätten wir ihn z.B. n genannt, sähe der Lambda-Ausdruck so aus: n -> n%2 == 0 und würde genauso funktionieren.
map()¶
Wir kennen bereits 2 Methoden über Stream: forEch() und filter(). Nun betrachten wir eine weitere Methode: map(). Mithilfe von map() können wir jedes Element eines Streams manipulieren, z.B. jedes Element quadrieren:
map() gibt, genau wie filter(), einen Stream zurück. forEch() hat als Rückgabetyp jedoch void! map() erwartet als Parameter eine Function, d.h. entweder ein Lambda-Audruck oder eine Methodenreferenz. filter() erwartet als Parameter ein Predicate. Ein Predicate ist eine Function, die ein boolean zurückgibt. forEach() erwartet als Parameter einen Consumer. Das ist eine Function deren Rückgabetyp void ist.