Funktionale Programmierung in Java: Von Lambdas bis zur Stream API
Funktionale Programmierung (FP) ist ein Paradigma, bei dem Berechnungen als Auswertung mathematischer Funktionen dargestellt werden, wobei Zustandsänderungen und Datenmutationen vermieden werden. Lange Zeit blieb Java eine streng objektorientierte Sprache, aber mit der Veröffentlichung von Java 8 (2014) machte sie einen großen Schritt in Richtung FP, indem sie Lambda-Ausdrücke, funktionale Interfaces und die Stream API hinzufügte. Heute sind diese Werkzeuge unverzichtbar für das Schreiben von prägnantem, lesbarem und nebenläufig-sicherem Code.
In diesem Artikel werden wir die Schlüsselkonzepte der funktionalen Programmierung in Java behandeln: von den Grundlagen der Lambdas bis zur fortgeschrittenen Sammlungsverarbeitung mit der Stream API. Das Material richtet sich an Entwickler, die bereits mit der grundlegenden Java-Syntax vertraut sind, aber moderne Ansätze erlernen möchten.
1. Lambda-Ausdrücke und funktionale Interfaces
Ein Lambda-Ausdruck ist eine anonyme Funktion (Methode ohne Namen), die als Argument übergeben oder in einer Variable gespeichert werden kann. In Java implementiert ein Lambda ein funktionales Interface – ein Interface mit genau einer abstrakten Methode (SAM – Single Abstract Method).
Syntax:
(Parameter) -> { Rumpf }Beispiel: Anstatt eine anonyme Klasse für einen Comparator zu erstellen:
// Alter Weg (Java 7)Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.length() - s2.length(); }};
// Neuer Weg (Java 8+)Comparator<String> comparatorLambda = (s1, s2) -> s1.length() - s2.length();Wichtige funktionale Interfaces in Java (Paket java.util.function):
Predicate<T>– prüft eine Bedingung (Methodetest).Function<T, R>– wandelt ein Objekt vom Typ T in R um (Methodeapply).Consumer<T>– führt eine Aktion an einem Objekt aus (Methodeaccept).Supplier<T>– liefert ein Objekt ohne Eingabeparameter (Methodeget).
Beispiel mit Predicate:
Predicate<Integer> isEven = n -> n % 2 == 0;System.out.println(isEven.test(4)); // trueSystem.out.println(isEven.test(5)); // falseMethodenreferenzen (Method References) sind eine Kurzschreibweise für Lambdas, wenn der Rumpf aus einem einzigen Methodenaufruf besteht:
// Lambdalist.forEach(item -> System.out.println(item));// Methodenreferenzlist.forEach(System.out::println);2. Stream API: Grundlegende Operationen
Die Stream API ist ein leistungsstarkes Werkzeug zur Verarbeitung von Datensequenzen (Sammlungen, Arrays, Dateien) in einem deklarativen Stil. Ein Stream speichert keine Daten, sondern beschreibt nur eine Pipeline von Operationen. Die Daten werden nicht verändert – jeder Schritt erzeugt einen neuen Stream.
Erstellen eines Streams:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");Stream<String> stream = names.stream(); // sequentiellStream<String> parallelStream = names.parallelStream(); // parallelIntermediäre Operationen (geben einen neuen Stream zurück):
filter(Predicate)– wählt Elemente nach einer Bedingung aus.map(Function)– transformiert jedes Element.sorted()/sorted(Comparator)– Sortierung.distinct()– entfernt Duplikate.limit(n)– begrenzt die Anzahl der Elemente.
Terminale Operationen (beenden den Stream, geben ein Ergebnis zurück):
collect(Collectors.toList())– sammelt in eine Sammlung.forEach(Con