Funktionale Programmierung mit Hilfe von Java Lambda Ausdrücken

Keylearnings:

  • Was sind Java Lambda Ausdrücke?
  • Was ist funktionale Programmierung?
  • Was sind funktionale Interfaces?
  • Was sind anonyme Klassen?
  • Wie du Java Lambda Ausdrücke in Collections verwendest?
  • Was ist ein Predicate?
  • Was sind Streams?

Ich koche nicht jeden Tag!

Gerne gehe ich auch in ein Baguette-Restaurant.

Mein Lieblingsrestaurant hat eine Theke mit frischen Zutaten: Paprika, Tomaten, Schinken, Salami und Peperoni. Hieraus bereitet die freundliche Bedienung die verschiedenen Baguettes aus der Speisekarte zu.

Am liebsten esse ich ein Schinken-Baguette mit Salatgurken, Zwiebeln und Peperoni.

Wenn ich meine Bestellung aufgebe, dann verläuft der Zubereitungsprozess wie folgt.

  1. Die Bedienung zerschneidet eine Salatgurke.
  2. Belegt das Baguette mit Schinken und Salatgurke.
  3. Ich werde gefragt. „Möchten Sie zusätzlich Peperoni?“

Die Frage ist:

Können wir ein Programm schreiben, welches den Vorgang der Baguette-Zubereitung übernimmt?

Wir kennen die objektorientierte Programmierung und es liegt nahe die Zutaten als Objekte aufzufassen.

Also erstellen wir entsprechende Klassen für die Salatgurke, den Schinken und die Baguettebrötchen usw.

Aber wie realisieren wir die Verarbeitung unserer Zutaten, sodass wir am Ende ein leckeres Baguette in der Hand halten?

Hierfür ist das Konzept der funktionalen Programmierung geeignet, das wir in Java mit Hilfe von sogenannten Lambda Ausdrücken realisieren können.

Und genau das wollen wir uns in diesem Artikel ansehen.

Was ist der Unterschied zwischen funktionaler und objektorientierter Programmierung?

Mit dem Konzept der objektorientierten Programmierung beschreiben wir die in unserem Szenario beteiligten Objekte und die funktionale Programmierung hilft uns dabei zu beschreiben was wir mit diesen Objekten tun.

Bei der funktionalen Programmierung steht also die Beschreibung der Aktion im Vordergrund.

Was ist funktionale Programmierung?

Mit Hilfe von funktionaler Programmierung können wir eine Art Produktionsstraße abbilden, auf der unterschiedliche Aktionen gleichzeitig stattfinden können. Diese Produktionsstraße nennt man einen Stream.

ACHTUNG JETZT WIRD ES KURZ THEORETISCH! Let’s talk about math 🙂

Der Begriff des Funktionals kommt aus der Mathematik. Ein Funktional transformiert ein Objekt in eine andere Gestalt.

Das besondere hierbei ist, dass die Dimension des transformierten Objektes sich verändern kann.

In dem Fall, dass die Dimension sich vergrößert oder gleich bleibt, sprechen wir von einem Mapping.

Verringert sich die Dimension, dann reden wir von einer Reduzierung.

Außerdem hat ein Funktional die Eigenschaft, dass dessen Ergebnis ausschließlich vom Input abhängt.

Okay, das war es für heute mit dem Mathematik-Unterricht.

AUF IN DIE PRAXIS!

Was hat das mit unseren Baguettes zutun? Naja, wir machen eigentlich mit unseren Zutaten nichts anderes.

Im ersten Schritt Mappen wir die Salatgurke und die  Zwiebel in eine geschnittene Salatgurke bzw. in eine geschnittene Zwiebel.

Anschließend reduzieren wir Salatgurke, Zwiebel, Schinken und Baguette-Brötchen zu einem Schinken-Baguette.

Java Lambda Ausdruk

Wichtig hierbei ist, dass das Mapping der Salatgurke und der Zwiebel parallel stattfinden kann (genügend Hände bzw. CPU’s vorausgesetzt). Die Reduzierung in ein Baguette aber erst erfolgen kann, sobald alle Zutaten vorbereitet sind.

Funktionale Interfaces

Funktionale und objektorientierte Programmierung gehören unterschiedlichen Programmierparadigmen an.

Bei der funktionalen Programmierung steht die Frage „Was gemacht werden soll?“ im Vordergrund.

Java ist allerdings eine objektorientierte Programmiersprache, bei der die beteiligten Objekte im Fokus stehen.

Um bei unserem Beispiel von oben zu bleiben bedeutet dies, dass bei der objektorientierten Programmierung die Salatgurke im Vordergrund steht und bei der funktionalen Programmierung der Fokus auf das zerschneiden der Gurke liegt.

Also wie können wir eine Brücke zwischen diesen Welten schlagen?

Die Lösung sind die funktionalen Interfaces.

Ein funktionales Interface ist ein normales Java Interface, das allerdings lediglich aus einer einzigen Methode besteht.

Durch diesen „Hack“ können wir die Funktionale Programmierung in die objektorientierte  JAVA Welt übertragen.

Und wie das geht, schauen wir uns jetzt an.

Seit JAVA 8 gibt es für diesen Zweck die sogenannten Lambda Ausdrücke.

Auf den ersten Blick scheinen Lambda Expressions lediglich eine Erweiterung bzw. Ablösung von anonymen Klassen zu sein. Daher sollten wir zunächst einen Blick auf diese werfen.

Anonyme Klassen

Wir beginnen mit der Definition eines funktionalen Interfaces ZutatVerarbeiten, das eine Methode schneiden() enthält, mit der wir unserer Zutaten zerkleinern können.

public interface ZutatVerarbeiten {
	public void schneiden();	
}

Damit wir überhaupt eine Zutat haben, die wir schneiden können, benötigen wir außerdem eine Klasse Zutat. Und hier wird es jetzt richtig interessant.

1: public class Zutat {
	
2:	public String name;

3:	public Zutat(String name) {
4:		this.name = name;
5:	}

6:	public String getName() {
7:		return name;
8:	}

9:	public void setName(String name) {
10:		this.name = name;
11:	}

12:	public boolean zubereiten(int anz, ZutatVerarbeiten z) {
13:		return z.schneiden(anz);
14:	}
15:}

Neben einem String-Attribut, in dem der Name der Zutat gespeichert wird, einem Konstruktor und den üblichen getter und setter Methoden enthält die Klasse eine Methode zubereiten(), der wir als Parameter eine Variable vom Typ unseres funktionalen Interfaces ZutatVerarbeiten übergeben.

Innerhalb der Methode zubereiten() rufen wir die in dem Interface deklarierte Methode schneiden() auf.

Allerdings ist im Interface ZutatenVerarbeiten lediglich die Methodensignatur definiert.

Wie verleiben wir der Methode zubereiten() eine Funktionalität ein?

Genau das passiert mit Hilfe von anonymen Klassen. Und das geht wie folgt!

1: Zutat z = new Zutat();
		
2: z.zubereiten(new ZutatVerarbeiten(){

3:	public void schneiden() {
4:	   System.out.println("Schneide Zwiebeln!");			
5:	}
			
6:  });

In Zeile eins erzeugen wir zunächst eine Instanz der Klasse Zutat. Anschließend rufen wir in Zeile zwei die Methode zubereiten auf.

Neu ist der Parameter! Hier übergeben wir eine sogenannte anonyme Klasse, die wir „On the fly“ erzeugen.

Wie wird eine anonyme Klasse erzeugt?

Obwohl es sich bei ZutatVerarbeiten um ein Interface handelt, verwenden wir das Schlüsselwort new um eine Instanz von ZutatVerarbeiten zu erzeugen.

Aber aus welcher Klasse wird die Instanz erzeugt?

Jetzt betritt der Programmcode zwischen den geschweiften Klammern (Zeile 2-6) die Bühne. Hier haben wir die Möglichkeit eine vollständige Klasse zu definieren, aus welcher das ZutatVerarbeiten Objekt erzeugt wird.

Auch wenn wir in der Klassendefinition das Schlüsselwort implements nicht verwenden, sorgt Java automatisch dafür, dass diese Klasse das ZutatVerarbeiten Interface implementiert.

In unserem Fall ist diese Klasse einfach gehalten und überschreibt lediglich die schneiden() Methode aus dem ZutatVerarbeiten Interface.

Innerhalb der Methode schneiden() geben wir nur eine Ausgabe auf dem Bildschirm aus.

Eine anonyme Klasse ist also nichts anderes als eine Klassendefinition ohne Namen, von der wir eine Instanz erzeugen.

Da unser Objekt lediglich eine einzige Methode enthält sprechen wir hierbei auch von einem Command bzw. Action Objekt.

Die Bildschirmausgabe dieses Programms ist:

Schneide Zwiebeln!

Okay, durchatmen! Was haben wir hier gemacht?

Wir haben eine einzelne Methode mit einem Interface ummantelt um diese anschließend als Action-Objekt in eine Methode einzuschleusen.

Herzlich willkommen in der Welt der funktionalen Programmierung! Das Action-Objekt haben wir hierbei mit Hilfe einer anonymen Klasse erzeugt.

Seit Java 8 gibt es allerdings einen weitaus kompakteren Weg um gleiches zu erreichen. Und zwar die sogenannten Lambda Ausdrücke.

Java Lambda Ausdrücke

Was sind JAVA Lambda Ausdrücke?

Schauen wir uns an wie wir obiges Beispiel mit Hilfe von JAVA Lambda Ausdrücken realisieren können.

Mit Lambda Ausdrücken können wir den kompletten Code der schneiden() Methode direkt als Parameter an die zubereiten() Methode übergeben. Und das geht wie folgt:

z.zubereiten(() -> {System.out.println("Schneide Zwiebeln")});

Hierbei nennen wir

() -> {System.out.println("Schneide Zwiebeln")}

einen Lambda Ausdruck.

Lambda Ausdrücke bestehen aus zwei, durch einen Pfeil voneinander, getrennten Teilen.

Den linken Teil des Ausdrucks können wir als Parameterliste interpretieren. Der rechte Teil des Ausdrucks ist die Implementierung der im funktionalen Interface definierten Methode.

Die Bildschirmausgabe obigen Aufrufs ist die gleiche wie in der über anonyme Klassen realisierten Implementierung. Nämlich

Schneide Zwiebeln!

Wie wird der Lambda Ausdruck interpretiert?

In unserem Beispiel erwartet die Methode zubereiten ein Argument vom Typ ZutatVerarbeiten, daher wird unser Lambda Ausdruck anhand der in diesem funktionalen Interface definierten Methode schneiden() ausgewertet.

Da die Methode keine Parameter erwartet, besteht der rechte Teil unseres Java Lambda Ausdrucks lediglich aus einem leeren Klammerausdruck (). Die Implementierung der schneiden() Methode entspricht der Bildschirmausgabe

{System.out.println("Schneide Zwiebeln")}

Parameter und Rückgabewerte in JAVA Lambda Ausdrücken verwenden

Nehmen wir an wir möchten unserer schneiden Methode die Anzahl der zu zerschneidenden Zwiebeln mitgeben.

Außerdem wollen wir dem Umstand Rechnung tragen, dass selbst das größte Baguette-Restaurant nur endlich viele Zwiebeln auf Lager hat und einen Wahrheitswert zurückliefern, der angibt, ob der Auftrag erfolgreich ausgeführt werden konnte.

Was müssen wir anpassen?

Genau vier Dinge!

  1. Wir müssen die schneiden() Methode in unserem funktionalen Interface ZutatVerarbeiten um einen Integer-Parameter und einen booleschen Rückgabewert erweitern.
  2. In der Klasse Zutat muss die Methode zubereiten um einen Integer-Parameter erweitert werden und einen booleschen Rückgabewert zurückliefern.
  3. Die Implementierung der schneiden() Methode mit Hilfe eines entsprechenden JAVA Lambda Ausdrucks formulieren.
  4. Aufruf der Methode zubereiten mit dem entsprechenden JAVA Lambda Ausdruck als Argument.

Beginnen wir damit das Interface ZutatVerarbeiten zu erweitern.

public interface ZutatVerarbeiten {

    public boolean schneiden(int anz);
	
} 

Dieser Schritt war leicht! Wir haben der Methoden-Deklaration von schneiden lediglich einen Integer-Parameter anz spendiert und den Rückgabewert von void auf boolean geändert.

Kommen wir zum nächsten Schritt und passen die Klasse Zutat an.

1: public class Zutat {

2:      public String name;

3:      public Zutat(String name) {
4:         this.name = name;
5:      }

6:      public String getName() {
7:         return name;
8:      }

9:      public void setName(String name) {
10:        this.name = name;
11:     }

12:     public boolean zubereiten(int anz, ZutatVerarbeiten z) {
13:	     return z.schneiden(anz);
14:	}
15:}

Hier haben wir die zubereiten Methode an die Änderungen des Interface ZutatatVerarbeiten aus Schritt eins angepasst.

Zu diesem Zweck haben wir die Methoden-Deklaration um einen Integer-Parameter erweitert. Außerdem geben jetzt wir einen booleschen Rückgabewert zurück.

Nun beginnt die echte Arbeit! Wir müssen einen Lambda Ausdruck formulieren, der die schneiden() Methode aus dem ZutatVerarbeiten Interface implementiert.

1: final int ANZ_ZWIEBEL = 10;
		
2: ZutatVerarbeiten zv = (a)-> {if (a <= ANZ_ZWIEBEL){
3:		System.out.println(a+" Zwiebel verarbeitet!");
4:		return true;
5:	}else{
6:		System.out.println("Nicht genügend Zwiebel auf Lager");
7:		return false;
8:	}
9:   };

Es beginnt unspektakulär mit der Definition einer Konstanten ANZ_ZWIEBEL, in der wir die Anzahl der vorhandenen Zwiebeln festlegen.

Ab Zeile zwei wird es spannend. Hier formulieren wir den Lambda Ausdruck, welcher die Implementierung der schneiden() Methode entspricht.

Diesen JAVA Lambda Ausdruck weisen wir der Variable zv vom Typ ZutatVerarbeiten zu. Dies hat zur Folge, dass der Lambda Ausdruck anhand der Methodensignatur schneiden() aus dem funkionalen Interface ZutatVerarbeiten interpretiert wird.

Da die Methode schneiden einen Integer-Parameter besitzt wird die Variable a aus der Parameterliste des Lambda Ausdrucks (also die linke Seite des Lambda Ausdrucks) als Integer-Variable interpretiert.

Der Rechte Teil des Lambda Ausdrucks stellt die Implementierung der schneiden() Methode dar.

Da diese Implementierung mehr als eine Zeile umfasst müssen wir den gesamten Codeblock mit geschweiften Klammern umschließen.

Innerhalb des Codeblocks prüfen wir mit Hilfe einer if Bedingung (Zeile 2), ob wir überhaupt genügend Zwiebeln auf Lager haben.

Ist diese Bedingung erfüllt geben wir die Ausgabe

a Zwiebel verarbeitet!  

auf dem Bildschirm aus wobei a dem Wert des Integerparameters entspricht, der die Anzahl der zu verarbeiteten Zwiebeln enthält.

Falls die Bedingung NICHT erfüllt ist geben wir in Zeile sechs die Ausgabe

Nicht genügend Zwiebel auf Lager!

auf dem Bildschirm aus.

Des Weiteren hat die Interface-Methode schneiden() einen booleschen Rückgabewert, daher geben wir im Falle, dass die Zwiebel erfolgreich geschnitten werden konnten in Zeile vier den Wahrheitswert true oder falls wir nicht mehr genügend Zwiebeln auf Lager hatten den Wahrheitswert false zurück.

Okay, durchatmen wir haben viel geschafft!

Jetzt fehlt nur noch der krönende Abschluss. Damit der Code unseres JAVA Lambda Ausdrucks ausgeführt wird müssen wir diesen als Argument an den Aufruf der zubereiten Methode der Klasse Zutat übergeben.

boolean erfolg = z.zubereiten(11,zv);

Hier rufen wir die zubereiten() Methode mit zwei Argumenten auf.

Das erste Argument ist der Integer-Parameter, welcher die Anzahl der zu verarbeitenden Zwiebel angibt.

Als zweiten Parameter übergeben wir die Variable zv, in der unser JAVA Lambda Ausdruck gespeichert ist. Da der zweite Parameter dem Typ unseres funktionalem Interfaces ZutatVerarbeiten entspricht, wird der Lambda Ausdruck als Implementierung der schneiden() Methode verwendet.

Den Wahrheitswert, welchen die Methode zubereiten zurückliefert speichern wir in der Variable erfolg.

Da wir die Methode mit dem Parameter 11 aufrufen, die Konstante ANZ_ZWIEBEL allerdings lediglich den Wert 10 hat, erhalten wir die Programmausgabe:

Nicht genügend Zwiebel auf Lager

Lambda Ausdrücke und Collections

Java Lambda Collections

Okay, ich weiß was du dich fragst? Wie können wir hiermit jetzt unsere Produktionsstraße realisieren?

Grundlage hierfür ist die Zusammenarbeit von Java Lambda Ausdrücken und Collections. Legen wir also eine Array-Liste, in der wir unsere Baguette Zutaten speichern an.

List zutaten = new ArrayList<>();
		
zutaten.add(new Zutat("Paprika"));
zutaten.add(new Zutat("Tomaten"));
zutaten.add(new Zutat("Schinken"));
zutaten.add(new Zutat("Salami"));
zutaten.add(new Zutat("Peperoni"));
zutaten.add(new Zutat("Zwiebel"));

Nichts was wir nicht schon kennen würden. Aber jetzt kommen die Lambda Ausdrücke ins Spiel!

Den Collections wurde eine Methode forEach spendiert, der wir als Parameter einen Lambda Ausdruck übergeben können, der auf jedes Element der Collection angewendet wird.

Um die Namen der Zutaten aus unserer Liste auf dem Bildschirm auszugeben, müssen wir die forEach Methode wie folgt anwenden.

zutaten.forEach(zutat -> System.out.println(zutat.getName()));

Die Bildschirmausgabe dieses Programmschnipsel ist:

Paprika
Tomaten
Schinken
Salami
Peperoni
Zwiebel

STOP! Haben wir nicht gesagt, dass Lambda Ausdrücke anhand von funktionalen Interfaces interpretiert werden? Aber welches funktionale Interface verwendet die forEach Methode?

Gut beobachtet! Um den an die forEach Methode übergebenen Lambda Ausdruck zu interpretieren benötigen wir ein Interface, das eine Methode enthält, die ein Objekt als Parameter akzeptiert und keinen Rückgabewert zurückliefert.

Und hier hilft uns das Paket java.util.function weiter, das eine ganze Sammlung an funktionalen Interfaces liefert, auf die wir zurückgreifen können.

Das Interface aus diesem Paket, welches unsere Anforderungen erfüllt, ist das sogenannte Consumer Interface, welches die Methode accept enthält, die als Parameter ein Objekt erwartet, keinen Rückgabewert liefert und unter der Haube von der forEach Methode für jedes Objekt in unserer Array-List Methode aufgerufen wird.

Also Merke: Wenn du einen JAVA Lambda Ausdruck, mit einem Objekt als Parameter und keinem Rückgabewert an eine Methode übergeben möchtest, dann kannst du das Consumer Interface verwenden und dir die Erstellung eines eigenen funktionalen Interfaces sparen.

Verwendung des java.util.function Pakets für eigene Methoden

Neben dem Consumer Interface enthält das java.util.function Paket noch viele weitere funktionale Interfaces, welche uns das definieren eigener Interfaces oft ersparen.

Leider können wir uns nicht alle Interfaces im Detail ansehen. Das würde den Rahmen dieses Artikels bei weitem sprengen. Exemplarisch wollen wir uns im folgenden eines der am meisten verwendeten Interfaces ansehen. Nämlich das Predicate Interface.

Das Predicate Interface!

Das Predicate Interface enthält die Methode test, welche ein Objekt als Argument erwartet und einen booleschen Rückgebewert liefert.

Hierdurch haben wir die Möglichkeit dynamisch Bedingungen als Argument an eine Methode zu übergeben.

Schauen wir uns auch das in der Praxis an.

Beginnen wir damit einen JAVA Lambda Ausdruck zu formulieren, der als Parameter ein Objekt erwartet und einen Wahrheitswert zurückliefert.

Predicate pred = (z)->z.size() > 5;

Mit diesem Lambda Ausdruck überprüfen wir, ob unsere Zutaten Liste mehr als fünf Elemente besitzt. Dass es sich bei dem Parameter z um eine Liste handelt legen wir mit Hilfe von Generics fest.

Der Einfachheit wegen verwenden wir zur Demonstration eine statische Methode, die in Abhängigkeit der Gültigkeit des Lambda Ausdrucks eine passende Bildschirmausgabe liefert.

1: public static void ausreichendZutaten(Predicate p, List zutaten){
2:	if (p.test(zutaten)){
3:		System.out.println("Ausreichend Zutaten vorhanden");
4:	}else{
5:		System.out.println("Nicht genügend Zutaten vorhanden");
6:	}
7:}

Die Methode hat zwei Parameter. Als erstes Argument können wir der Methode den in der Variable pred gespeicherten Lambda Ausdruck übergeben. Der zweite Methoden-Parameter entspricht der Liste, die wir im Codeblock unseres Lambda Ausdrucks untersuchen wollen.

Am interessantes innerhalb dieser Methode ist die Bedingung der if Anweisung in Zeile zwei. Hier wird die test Methode des Predicate Interfaces aufgerufen. Dieser Aufruf führt zur Ausführung des im übergebenen Lambda Ausdrucks definierten Codeblocks.

In Abhängigkeit des zurückgelieferten Wahrheitswert wird eine entsprechende Bildschirmausgabe erzeugt.

Genug gequatscht! Lass es uns ausprobieren und unsere Methode ausreichendZutaten mit dem formulierten Java Lambda Ausdruck und unserer zutaten Array-Liste aufrufen.

ausreichendZutaten(pred,zutaten);

Da unsere zutaten Array-List sechs Elemente enthält, liefert die Auswertung des Lambda Ausdrucks den Wahrheitswert true zurück und wir erhalten die Programmausgabe.

Ausreichend Zutaten vorhanden

Was sind Streams?

Java Lambda Ausdruck Stream

Zum Abschluss wollen wir den Gedanken von der Produktionsstraße nochmal kurz aufgreifen.

Dieses Konzept können wir mit Hilfe von sogenannten Streams realisieren.

Die Streams ermöglichen es uns die Elemente einer Collection zu bearbeiten ohne über diese zu iterieren zu müssen.

Okay, verdeutlichen wir uns das ganzen an einem Beispiel.

Aus unserer Zutatenliste möchten wir die Zwiebel und die Tomaten schneiden.

Klassisch müssten wir über die gesamte Liste iterieren und bei jedem Element überprüfen, ob es sich um eine Tomate oder Zwiebel handelt.

Streams ermöglichen es uns, dass wir uns gleich auf die relevanten Elemente konzentrieren und nur diese bearbeiten, was insbesondere bei einer großen Datenmengen deutlicher effizienter ist.

Hierfür stellen die Streams einen sogenannten Filter zur Verfügung. Schauen wir uns folgenden Programmschnipsel an.

1: Predicate p = (z)->(z.getName().equals("Zwiebel") || z.getName().equals("Tomaten")); 
2: zutaten.stream().filter(p).forEach((z)->System.out.println(z.getName()));

In Zeile eins definieren wir ein Predicate mit der Bedingung, dass das Namensattribut der Zutat z entweder einer Tomate oder einer Zwiebel entspricht.

Anschließend verwenden wir einen Stream, der mit dem Aufruf der Collection Methode stream() eingeleitet wird um unsere zutaten Array-List auf die im Predicate p festgelegten Bedingungen einzuschränken.

Der Aufruf der Methode stream() liefert ein Objekt, das unter anderem die Methode filter zur Verfügung stellt. Die Filtermethode erwartet ein Predicate als Argument.

Die angehängte forEach Methode dient in unserem Beispiel lediglich dazu um die Wirkung der filter() Methode zu demonstrieren.

Und Tatsächlich beschränkt sich die Bildschirmausgabe auf die Elemente mit dem Namen „Tomaten“ und „Zwiebeln“.

Tomaten
Zwiebel

Neben der filter Methode besitzt ein Stream noch viele weitere Methoden, so z.B. die map Methode, die als Parameter ein Objekt als Parameter erwartet und dieses Objekt modifiziert zurückliefert.

Ein Beispiel hierfür ist z.B. die Umwandlung eines Strings in Großbuchstaben.

Okay, erweitern wir also unseren Stream um den Aufruf der map Methode, mit der wir unsere Zutaten verarbeiten.

zutaten.stream().filter(p).map((z)->{z.setName(z.getName()+" verarbeitet");
			return z;}).forEach((z)->System.out.println(z.getName()));

Den Lambda Ausdruck, den wir der map Methode als Parameter übergeben, erweitert das Namensattribut der Zutat z um den Zusatz „verarbeitet“, sodass die Bildschirmausgabe dieses Programmschnipsel

Tomaten verarbeitet
Zwiebel verarbeitet

lautet.

Auch hier stellt sich wieder die Frage nach dem von der map Methode verwendeten funktionalen Interface. Wieder können wir uns aus dem Java.util.function Paket bedienen.

In diesem Fall benötigen wir ein Interface, welches ein Objekt als Parameter erwartet und das gleiche Objekt modifiziert zurückliefert. Und genau das leistet das Function Interface.

Einen Ausdruck wie wir ihn in Zeile zwei unseres Codes verwenden nennen wir eine Verkettung von Methoden. Hierbei unterscheiden wir zwischen terminalen und nicht terminalen Methoden.

Terminale Methoden schließen den Stream ab und führen zur Ausführung der Methodenkette. Wir sprechen in diesem Zusammenhang auch von Lazy Validation.

In unserem Beispiel ist dir forEach Methode die terminale Methode, welche zur Ausführung des Streams führt. Wichtig hierbei ist zu beachten, dass es sich bei der forEach Methode um eine Stream-Methode handelt und nichts mit der gleichnamigen Methode aus den Collections zu tun hat.

Und hiermit sind wir wieder am Anfang des Artikels. Die Erstellung unseres Baguettes.

Im ersten Schritt reduzieren wir die Zutaten auf die vom Kunden gewünschten Zutaten (Möchten Sie gerne Peperonie?). Wir führen also eine Filterung durch.

Anschließend mappen wir unsere Zutaten auf die verarbeitenden Zutaten (geschnittene Salatgurke).

Im letzten Schritt führen wir dann eine terminale Operation aus, die dass fertige Baguette an den Kunde ausliefert.

Fazit: In diesem Artikel habe ich dir einen Überblick darüber gegeben wie du mit Hilfe von JAVA Lambda Ausdrücken funktional Programmieren kannst. Du hast gelernt wie man mit Hilfe von Lambda Ausdrücken Codeblöcke in Methoden injiziert!

Zugegeben Java Lambda Ausdrücke und insbesondere deren Anwendungen sind ein Thema für ein dickes Buch. Aber ich hoffe ich konnte die in diesem Artikel ein Gefühl für dieses mächtige Werkzeug vermitteln.

Ich freue mich auf deine Fragen im Kommentarbereich!

Hat dir der Artikel gefallen? Dann folge uns doch am besten gleich auf Facebook!

Hallo ich bin Kim und ich möchte ein großer Programmierer werden. Machst du mit?

Kommentare (2)

  • Antworte

    Hallo ,

    also das Beispiel der anonymen Klasse benötigt auch dasselbe Interface wie das funktionale Beispiel.

    Gruß,

    Carlo

    • Hallo Carlo,

      ja, um funkional Programmieren zu können brauchen wir in jedem Fall ein funktionales Interface. Um das Beispiel mit anonymen Klassen und mit Lambdas möglichst analog aufzubauen habe ich in beiden Fälle das gleiche Interface verwendet. Im Falle der Lambdas hätte man auch auf die mit Java mitgelieferten Interfaces aus java.util.function zurückgreifen können.

      Viele Grüße
      Kim

Hinterlasse ein Kommentar