Funktionale Programmierung in Java: von A bis Z
Funktionale Programmierung (FP) ist ein Paradigma, bei dem Berechnungen als Auswertung mathematischer Funktionen betrachtet werden, wobei Zustandsänderungen und veränderliche Daten vermieden werden. Java, traditionell eine imperative Sprache, führt seit Version 8 aktiv Elemente der FP ein. Dies ermöglicht es, prägnanteren, vorhersagbareren und leichter testbaren Code zu schreiben, insbesondere bei der Arbeit mit Sammlungen und asynchronen Operationen.
In diesem Artikel werden wir die wichtigsten Konzepte der FP in Java behandeln: von Lambda-Ausdrücken bis hin zu fortgeschrittenen Techniken mit der Stream API und Optional. Sie erfahren, wie Sie diese Werkzeuge in der Praxis einsetzen, um Ihren Code sauberer und effizienter zu gestalten.
1. Grundlagen: Lambda-Ausdrücke und funktionale Schnittstellen
Lambda-Ausdruck ist eine kurze Schreibweise für eine anonyme Methode (Funktion). Er ermöglicht es, Verhalten als Daten zu übergeben. Syntax: (Parameter) -> Körper.
Funktionale Schnittstelle ist eine Schnittstelle mit genau einer abstrakten Methode (SAM – Single Abstract Method). Nur solche Schnittstellen können mit Lambdas implementiert werden. Wichtige eingebaute Schnittstellen in java.util.function:
Predicate<T>– nimmt ein Objekt T entgegen, gibt boolean zurück (Methodetest).Function<T, R>– nimmt T entgegen, gibt R zurück (Methodeapply).Consumer<T>– nimmt T entgegen, gibt nichts zurück (Methodeaccept).Supplier<T>– nimmt nichts entgegen, gibt T zurück (Methodeget).UnaryOperator<T>– ein Spezialfall von Function, bei dem T und R vom gleichen Typ sind.
Beispiel: Sortieren einer Liste mit Comparator (funktionale Schnittstelle)
import java.util.*;
public class LambdaExample { public static void main(String[] args) { List<String> names = Arrays.asList("Ivan", "Anna", "Peter", "Maria");
// Ohne Lambda (anonyme Klasse) Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } });
// Mit Lambda Collections.sort(names, (a, b) -> a.length() - b.length());
// Noch kürzer: Verwende Comparator.comparingInt names.sort(Comparator.comparingInt(String::length));
System.out.println(names); // [Anna, Peter, Ivan, Maria] }}Beispiel: Verwendung von Predicate zur Filterung
import java.util.function.Predicate;
public class PredicateExample { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isEven.test(4)); // true System.out.println(isEven.and(isPositive).test(-2)); // false (nicht positiv) System.out.println(isEven.or(isPositive).test(-3)); // true (nicht gerade, aber positiv? nein, -3 ist nicht positiv) }}2. Stream API: Ein leistungsstarkes Werkzeug zur Datenverarbeitung
Stream API ist eine Sequenz von Elementen, die parallele und sequentielle Operationen unterstützt. Ein Stream speichert keine Daten, sondern transformiert sie. Die wichtigsten Operationen werden unterteilt in intermediäre (geben einen neuen Stream zurück) und terminale (schließen die Verarbeitung ab und geben ein Ergebnis oder einen Nebeneffekt zurück).
Grundlegende Operationen der Stream API:
- Intermediär:
filter,map,flatMap,sorted,distinct,peek,limit,skip. - Terminal:
collec