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
.numbers
ist vom TypList
.List
ist ein Interface, das vom InterfaceCollection
erbt. Die Methodestream()
gibt einenStream
zurück. - Ein
Stream
ist ein sogenannter Spliterator und vereinigt zwei Konzepte:- Ein
Spliterator
ist einerseits einIterator
und 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
println
wird 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 -> expression
Ein 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.