4. Java 8 Lambda-Ausdrücke
4.1. Beispiel 01 – Funktionale Schnittstellen und Lambdas
![]() |
Betrachten Sie den folgenden Code:
package dvp.java8.lambdas;
public class Exemple01 {
public static void main(String[] args) {
// anonymous classes
I1 ia1 = new I1() {
@Override
public void doSomething() {
System.out.println("ia1.doSomething");
}
};
I2 ia2 = new I2() {
@Override
public String getSomething(double value) {
return String.format("ia2.getSomething(%s)", value);
}
};
// lambdas
I1 ib1 = () -> System.out.println("ib1.lambda");
I2 ib2 = (value) -> String.format("ib2.lambda(%s)", value);
I1 ib3 = () -> {
System.out.println("ib3.lambda");
};
I2 ib4 = (double value) -> {
return String.format("ib4.lambda(%s)", value);
};
// app
ia1.doSomething();
System.out.println(ia2.getSomething(4.3));
ib1.doSomething();
System.out.println(ib2.getSomething(5.8));
ib3.doSomething();
System.out.println(ib4.getSomething(10.1));
}
}
@FunctionalInterface
interface I1 {
void doSomething();
}
@FunctionalInterface
interface I2 {
String getSomething(double value);
}
- Zeilen 41–44: Definieren Sie eine funktionale Schnittstelle I1. Eine funktionale Schnittstelle ist eine Schnittstelle mit nur einer Methode. Sie ist nicht vom Vorhandensein der Annotation [@FunctionalInterface] in Zeile 41 abhängig, die optional ist;
- Zeilen 46–49: eine zweite funktionale Schnittstelle I2;
- Zeilen 6–19: Die Schnittstellen I1 und I2 werden mithilfe anonymer Klassen implementiert, was vor der Einführung von Lambda-Funktionen die gängigste Lösung war;
- Zeilen 21–29: Die Schnittstellen I1 und I2 werden mithilfe von Lambda-Funktionen implementiert;
- Zeilen 7–12: Implementierung der Schnittstelle I1 mit einer anonymen Klasse. Die Syntax für die Implementierung einer Schnittstelle I mit einer anonymen Klasse lautet wie folgt:
wobei m1, m2, ... die durch die Schnittstelle I definierten Methoden sind.
- Zeilen 14–19: Implementierung der Schnittstelle I2 mit einer anonymen Klasse;
- Zeile 22: Implementierung der Schnittstelle I1 mit einer Lambda-Funktion. Hier nutzen wir die Tatsache, dass die funktionale Schnittstelle nur eine Methode hat. Die Lambda-Funktion implementiert dann diese einzelne Methode M. Ihre Syntax lautet wie folgt:
Die Typen T1, T2, Tn können weggelassen werden, wenn der Compiler sie aus dem Kontext ableiten kann (Typinferenz).
- Zeile 22: Implementierung der Methode [I1.doSomething] mit der Signatur:
void doSomething();
[doSomething] ist eine Methode, die keine Parameter hat und kein Ergebnis zurückgibt. Ihre Lambda-Implementierung kann wie in Zeile 22 oder wie in den Zeilen 24–26 geschrieben werden, d. h., der Code der Lambda-Funktion kann in geschweifte Klammern gesetzt werden. Wenn dieser Code wie hier nur eine einzige Anweisung enthält, können diese geschweiften Klammern weggelassen werden;
- Zeile 23: Implementierung der Methode [I1.getSomething] mit folgender Signatur:
String getSomething(double value);
[getSomething] nimmt einen Parameter vom Typ [double] entgegen und gibt ein Ergebnis vom Typ [String] zurück. Die Lambda-Implementierung kann entweder die in Zeile 23 oder die in den Zeilen 27–29 sein. In der Implementierung in Zeile 23:
- wird der Typ des Parameters [value] weggelassen. Es wird dann der Typ [double] verwendet, der in der Signatur von [getSomething] zu finden ist;
- ist der Lambda-Code nicht in Klammern gesetzt. Das Ergebnis des Lambda ist dann der Wert des einzigen Ausdrucks in diesem Code, hier: String.format("ib2.lambda(%s)", value);
In der Implementierung in den Zeilen 27–29:
- wird der Typ des Parameters [value] explizit deklariert;
- verwenden wir ein [return], um das Ergebnis des Lambda zurückzugeben. In diesem Fall müssen geschweifte Klammern verwendet werden;
- Zeilen 32–37: Wir rufen die verschiedenen anonymen Funktionen und Lambdas auf;
Das erhaltene Ergebnis lautet wie folgt:
4.2. Beispiel-02 – Die funktionale Schnittstelle Predicate<T>
![]() |
Meistens haben wir es mit funktionalen Schnittstellen aus Bibliotheken zu tun und nicht mit solchen, die wir selbst definieren. Hier interessiert uns die funktionale Schnittstelle [Predicate], die im Paket [java.util.function] definiert ist, welches die meisten funktionalen Schnittstellen in Java 8 enthält. Sie ist wie folgt definiert:

Wir haben erwähnt, dass eine funktionale Schnittstelle nur eine Methode hat. Hier gibt es jedoch vier. Eine weitere Neuerung, die mit Java 8 eingeführt wurde, war das Konzept einer Standardmethode in einer Schnittstelle, gekennzeichnet durch das Schlüsselwort [default]. Hier haben wir drei solcher Methoden. Diese Methoden zeichnen sich dadurch aus, dass sie über eine Standardimplementierung verfügen. Es besteht daher keine Verpflichtung für eine Klasse, die eine Schnittstelle mit Standardmethoden implementiert, diese auch zu implementieren. Somit muss eine Klasse, die die Schnittstelle [Predicate] implementieren möchte, nur eine einzige Methode implementieren: die Methode [test]. Die Schnittstelle [Predicate] ist daher tatsächlich eine funktionale Schnittstelle. Wir können somit sagen, dass eine funktionale Schnittstelle eine Schnittstelle ist, deren Implementierung nur eine einzige obligatorische Methode enthält. Wenn sie mehr als eine Methode hat, müssen die anderen das Schlüsselwort [default] tragen.
Die Methode [Predicate<T>.test] nimmt einen Parameter vom Typ T entgegen und gibt einen booleschen Wert zurück. Diese Schnittstelle wird im Allgemeinen zum Filtern von Sammlungen verwendet. Um ihre Verwendung zu veranschaulichen, verwenden wir die folgenden Daten:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Personne {
public enum Sexe {HOMME,FEMME};
// data
private String nom;
private int age;
private double poids;
private Sexe sexe;
// manufacturers
public Personne() {
}
public Personne(String nom, Sexe sexe, int age, double poids) {
this.nom = nom;
this.sexe=sexe;
this.age = age;
this.poids = poids;
}
// getters and setters
...
// toString
@Override
public String toString(){
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- Zeilen 32–38: Die Methode [toString] gibt die JSON-Zeichenkette der Person zurück;
Die Klasse [People] definiert eine Liste mit 3 Personen:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
public class Personnes {
private static List<Personne> personnes = Arrays.asList(new Personne("jean", Personne.Sexe.HOMME, 20, 70),
new Personne("marie", Personne.Sexe.FEMME, 10, 30), new Personne("camille", Personne.Sexe.FEMME, 30, 55));
public static List<Personne> get() {
return personnes;
}
public static String toString(List<Personne> liste) {
try {
return new ObjectMapper().writeValueAsString(liste);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- Zeilen 10–11: die Liste mit 3 Personen;
- Zeilen 13–15: eine statische Methode zum Abrufen dieser Liste;
- Zeilen 17–23: eine statische Methode zum Abrufen der JSON-Zeichenkette einer als Parameter übergebenen Liste von Personen;
Diese Daten werden vom folgenden Code verwendet:
package dvp.java8.lambdas;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple02 {
public static void main(String[] args) {
// predicate implemented by anonymous class
Predicate<Personne> filterPoids=new Predicate<Personne>() {
@Override
public boolean test(Personne personne) {
return personne.getPoids()<50;
}
};
// predicate implemented by a lambda
Predicate<Personne> filterAge = p -> p.getAge() < 28;
// list of persons
List<Personne> personnes = Personnes.get();
// displays
System.out.println(Personnes.toString(filterPersonnes(personnes, filterAge)));
System.out.println(Personnes.toString(filterPersonnes(personnes, filterPoids)));
}
private static List<Personne> filterPersonnes(List<Personne> personnes, Predicate<Personne> filter) {
// [filter] filters the [people] list
List<Personne> personnesFiltrées = new ArrayList<>();
for (Personne p : personnes) {
if (filter.test(p)) {
personnesFiltrées.add(p);
}
}
return personnesFiltrées; }
}
- Zeilen 13–18: Implementierung der Schnittstelle Predicate<Person> mithilfe einer anonymen Klasse. Dies ist ein Filter, der auf dem Gewicht der Person basiert;
- Zeile 20: Implementierung der Schnittstelle Predicate<Person> mithilfe einer Lambda-Funktion. Dies ist ein Filter, der auf dem Alter der Person basiert. Basierend auf dem Gesagten hätte dies auch wie folgt geschrieben werden können:
Predicate<Personne> filterAge = (Personne p) -> {
return (p.getAge() < 28);
};
aber die Version in Zeile 20 ist prägnanter. Der Typ des Parameters p wird aus dem Kontext abgeleitet. Hier konstruieren wir einen Typ [Predicate<Person>]. Die implementierte Methode hat dann die Signatur [boolean test(Person param)]. Daher ist der implizite Typ von p in Zeile 20 der Typ [Person];
- Zeile 22: Wir rufen eine vordefinierte Liste von Personen ab;
- Zeile 24: Wir filtern sie nach Alter;
- Zeile 25: Wir filtern sie nach Gewicht. In beiden Fällen zeigen wir die JSON-Zeichenkette der gefilterten Liste an;
- Zeilen 28–37: Eine statische Methode, die
- als Parameter eine Liste der zu filternden Personen und den Filter entgegennimmt. Der Filter ist eine Instanz der Schnittstelle [Predicate<Person>]. Der Einfachheit halber bezeichnen wir hier und an anderen Stellen im Dokument eine Instanz einer Schnittstelle I als Instanz einer Klasse C, die I implementiert;
- gibt die gefilterte Liste als Ergebnis zurück;
- Zeile 32: Wir verwenden die Methode [test] der Schnittstelle [Predicate]. Je nach dem an die Methode übergebenen Filter ist die Methode [test]:
return personne.getPoids()<50;
oder
return p.getAge() < 28
Die Ausführung der Klasse [Exemple02] liefert folgendes Ergebnis:
4.3. Beispiel-03 – Die funktionale Schnittstelle Function<T,R>
![]() |
Die funktionale Schnittstelle Function<T,R> ist wie folgt definiert:

Die einzige Methode der Schnittstelle hat die Signatur R apply(T t). Sie wird im Allgemeinen verwendet, um aus einem Collection<T>-Typ einen neuen Collection<R>-Typ zu erstellen. Zur Veranschaulichung dieser Schnittstelle verwenden wir den folgenden Code:
package dvp.java8.lambdas;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dvp.data.Personne;
import dvp.data.Personnes;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class Exemple03 {
public static void main(String[] args) throws JsonProcessingException {
// implementation with anonymous class
Function<Personne, String> mapToName = new Function<Personne, String>() {
@Override
public String apply(Personne personne) {
return personne.getNom();
}
};
// implementation with lambda
Function<Personne, Integer> mapToAge = p -> p.getAge();
// list of persons
List<Personne> personnes = Personnes.get();
// jSON
ObjectMapper jsonMapper = new ObjectMapper();
// displays
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
}
// transformation List<Person> --> List<T>
private static <T> List<T> mapPersonnes(List<Personne> personnes, Function<Personne, T> mapper) {
List<T> maps = new ArrayList<>();
for (Personne p : personnes) {
maps.add(mapper.apply(p));
}
return maps;
}
}
- Zeilen 15–20: Wir implementieren die Schnittstelle [Function<Person, String>] mit einer anonymen Klasse, die die Transformation von Person nach String durchführt;
- Zeile 22: Wir implementieren die Schnittstelle [Function<Person, Integer>] mit einer Lambda-Funktion, die die Transformation von Person nach Integer durchführt;
- Zeilen 33–39: Eine statische Methode, die
- zwei Parameter entgegennimmt: Der erste vom Typ List<Person> ist eine Liste der zu transformierenden Personen. Der zweite vom Typ Function<Person, T> ist eine Funktion, die jede Person aus der Liste entgegennimmt und ein Objekt vom Typ T erstellt;
- gibt eine Liste vom Typ List<T> zurück, wobei jedes Element das Ergebnis der Transformation von Person nach T ist;
- Zeilen 35–37: Die Transformation „Person -> T“ wird angewendet. Ist der zweite Parameter der Methode das Objekt [mapToName], wird eine Transformation „Person -> String“ durchgeführt. Ist es das Objekt [mapToAge], wird eine Transformation „Person -> Integer“ durchgeführt;
Das Ergebnis lautet wie folgt:
4.4. Beispiel-04 – Die funktionale Schnittstelle Consumer<T>
![]() |
Die funktionale Schnittstelle Consumer<T> ist wie folgt definiert:

Die einzige Methode der Schnittstelle hat die Signatur: void accept(T t). Diese Methode verarbeitet (konsumiert) ihren Parameter und gibt kein Ergebnis zurück. Zur Veranschaulichung verwenden wir den folgenden Code:
package dvp.java8.lambdas;
import java.util.List;
import java.util.function.Consumer;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple04 {
public static void main(String[] args) {
// list of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
Consumer<Personne> consumerAge = new Consumer<Personne>() {
@Override
public void accept(Personne personne) {
System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// immplémentaton lambda
Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());
// displays
for (Personne p : personnes) {
consumerAge.accept(p);
}
System.out.println("--------");
for (Personne p : personnes) {
consumerPoids.accept(p);
}
}
}
- Zeilen 14–19: Die Schnittstelle [Consumer<Person>] wird durch eine anonyme Klasse implementiert, deren Methode [accept] den Namen und das Alter der Person anzeigt;
- Zeile 21: Die Schnittstelle [Consumer<Person>] wird durch eine Lambda-Funktion implementiert, deren implizite Methode [accept] den Namen und das Gewicht der Person anzeigt;
- Zeilen 23–25: Die Liste der Personen wird von der Implementierung [consumerAge] verarbeitet;
- Zeilen 27–29: Die Liste der Personen wird von der Implementierung [consumerWeight] verarbeitet;
Die Ergebnisse lauten wie folgt:
4.5. Beispiel-05 – Die funktionale Schnittstelle BiConsumer<T,U>
![]() |
Die funktionale Schnittstelle BiConsumer<T,U> ist wie folgt definiert:

Die einzige Methode der Schnittstelle hat die Signatur: void accept(T t, U u). Sie verarbeitet den Typ T mit der Zusatzinformation U u. Wir veranschaulichen ihre Verwendung mit dem folgenden Code:
package dvp.java8.lambdas;
import dvp.data.Personne;
import dvp.data.Personnes;
import java.util.List;
import java.util.function.BiConsumer;
public class Exemple05 {
public static void main(String[] args) {
// list of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
BiConsumer<Personne, Integer> biconsumerAge = new BiConsumer<Personne, Integer>() {
@Override
public void accept(Personne personne, Integer integer) {
personne.setAge(personne.getAge() + integer);
System.out.printf("age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// lambda implementation
BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
p.setPoids(p.getPoids() + i);
System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
};
// displays
for (Personne p : personnes) {
biconsumerAge.accept(p, 100);
}
System.out.println("--------");
for (Personne p : personnes) {
biconsumerPoids.accept(p, 200);
}
}
}
- Zeilen 14–20: Implementierung der BiConsumer<T,U>-Schnittstelle mithilfe einer anonymen Klasse. Die [apply]-Methode verwendet ihren zweiten Parameter, um das Alter der als ersten Parameter übergebenen Person zu aktualisieren. Anschließend wird das Ergebnis angezeigt;
- Zeilen 22–25: Implementierung der BiConsumer<T,U>-Schnittstelle mithilfe einer Lambda-Funktion. Die implizite [apply]-Methode verwendet ihren zweiten Parameter, um das Gewicht der als ersten Parameter übergebenen Person zu aktualisieren. Anschließend wird das Ergebnis angezeigt;
- Zeilen 27–29: Die Liste der Personen wird mithilfe der [biconsumerAge]-Implementierung verarbeitet;
- Zeilen 31–33: Die Liste der Personen wird mithilfe der [biconsumerWeight]-Implementierung verarbeitet;
Die erhaltenen Ergebnisse lauten wie folgt:
4.6. Beispiel-06 – Die funktionale Schnittstelle BiFunction<T,U,R>
![]() |
Die funktionale Schnittstelle BiFunction<T,U,R> ist wie folgt definiert:

Die einzige Methode der Schnittstelle hat die Signatur: R apply(T t, U u). Diese Methode ähnelt der Methode [BiConsumer.apply], doch während letztere kein Ergebnis zurückgibt, gibt die Methode [BiFunction.apply] ein Ergebnis zurück. Wir veranschaulichen ihre Verwendung anhand des folgenden Codes:
package dvp.java8.lambdas;
import java.util.List;
import java.util.function.BiFunction;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple06 {
public static void main(String[] args) {
// list of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
@Override
public Integer apply(Personne personne, Integer integer) {
return personne.getAge() + integer;
}
};
// lambda implementation
BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
return p.getPoids() + i;
};
// displays
for (Personne p : personnes) {
System.out.printf("age de %s = %s%n", p.getNom(), biFunctionAge.apply(p, 100));
}
System.out.println("--------");
for (Personne p : personnes) {
System.out.printf("poids de %s = %s%n", p.getNom(), biFunctionPoids.apply(p, 200));
}
}
}
- Zeilen 14–19: Die Schnittstelle BiFunction<Person, Integer, Integer> wird mithilfe einer anonymen Klasse implementiert. Ihre Methode [apply] gibt das Alter der als ersten Parameter übergebenen Person zurück, erhöht um den Wert des zweiten Parameters;
- Zeilen 21–23: Die Schnittstelle `BiFunction<Person, Integer, Double>` wird mithilfe einer Lambda-Funktion implementiert. Die Methode `[apply]` gibt das Gewicht der als ersten Parameter übergebenen Person zurück, erhöht um den Wert des zweiten Parameters;
- Zeilen 25–27: Die Implementierung von [biFunctionAge] wird auf die Personen angewendet;
- Zeilen 29–31: Die Implementierung von [biFunctionWeight] wird auf die Personen angewendet;
Die erhaltenen Ergebnisse lauten wie folgt:
age de jean = 120
age de marie = 110
age de camille = 130
--------
poids de jean = 270.0
poids de marie = 230.0
poids de camille = 255.0
Zusätzlich zu Lambda-Funktionen wurde in Java 8 der Typ Stream<T> eingeführt, der einen Strom von Elementen des Typs T modelliert. Diese Elemente können aufeinanderfolgenden Transformationen unterzogen werden, die durch Lambda-Funktionen implementiert werden. Wenn möglich und bei Vorhandensein mehrerer Prozessoren können diese Transformationen manchmal parallel ausgeführt werden.
4.7. Beispiel-07 – Die funktionale Schnittstelle Supplier<T>
![]() |
Die funktionale Schnittstelle „Supplier<T>“ ist wie folgt definiert:

Die einzige Methode der Schnittstelle hat die Signatur: T get(), deren Aufgabe es ist, ein Objekt vom Typ T zurückzugeben.
Wir veranschaulichen diese funktionale Schnittstelle mit dem folgenden Code:
package dvp.java8.lambdas;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple07 {
public static void main(String[] args) {
// anonymous implementation
Supplier<Personne> supplier = new Supplier<Personne>() {
// list of persons
List<Personne> personnes = Personnes.get();
// interface implementation
@Override
public Personne get() {
int i = new Random().nextInt(personnes.size());
return personnes.get(i);
}
};
// operation
for (int i = 0; i < 5; i++) {
affiche(supplier);
}
}
// person display
public static void affiche(Supplier<Personne> supplier) {
System.out.println(supplier.get());
}
}
- Zeilen 13–28: Implementierung eines Typs Supplier<Person>;
- Zeilen 31–33: Die statische Methode [display] erwartet einen Parameter vom Typ Supplier<Person>;
- Zeilen 25–27: Verwendung der Instanz Supplier<Person>;
Es werden folgende Ergebnisse erhalten:






