Funktionale Programmierung in Java: Fortgeschrittenes Niveau
Funktionale Programmierung (FP) ist ein Paradigma, bei dem Berechnungen als Auswertung mathematischer Funktionen betrachtet werden, die Zustandsänderungen und veränderliche Daten vermeiden. Java, obwohl eine objektorientierte Sprache, hat mit der Veröffentlichung von Java 8 leistungsstarke Werkzeuge für FP erhalten: Lambda-Ausdrücke, das Stream API und funktionale Interfaces. Auf fortgeschrittenem Niveau ermöglichen diese Funktionen das Schreiben von prägnanterem, sichererem und ausdrucksstärkerem Code, insbesondere bei der Arbeit mit Collections, asynchronen Operationen und der Fehlerbehandlung.
In diesem Artikel werden wir fortgeschrittene Techniken der funktionalen Programmierung in Java betrachten: Funktionskomposition, Monaden (Optional und CompletableFuture), das Stream API mit paralleler Verarbeitung und Currying.
1. Funktionskomposition und funktionale Interfaces
In Java sind funktionale Interfaces Interfaces mit einer einzigen abstrakten Methode (SAM). Die am häufigsten verwendeten sind: Function<T, R>, Predicate<T>, Consumer<T> und Supplier<T>. Die fortgeschrittene Nutzung umfasst die Komposition von Funktionen mit den Methoden andThen() und compose().
Beispiel: Transformationskette
import java.util.function.Function;
public class FunctionComposition { public static void main(String[] args) { // Funktion: String -> seine Länge Function<String, Integer> lengthFunction = String::length; // Funktion: Zahl -> ihr Quadrat Function<Integer, Integer> squareFunction = x -> x * x;
// Komposition: String -> Länge -> Quadrat der Länge Function<String, Integer> composedFunction = lengthFunction.andThen(squareFunction);
String text = "Hello, Java!"; int result = composedFunction.apply(text); // 12 * 12 = 144 System.out.println("Ergebnis: " + result); // 144 }}Die Methode andThen() wendet zuerst die erste Funktion an, dann die zweite. compose() macht das Gegenteil: Es wendet zuerst die übergebene Funktion an, dann die erste. Dies ermöglicht den Aufbau flexibler und wiederverwendbarer Transformationsketten.
Praktisches Beispiel: Filtern und Transformieren einer Liste
import java.util.List;import java.util.function.Function;import java.util.function.Predicate;import java.util.stream.Collectors;
public class PredicateAndFunction { public static void main(String[] args) { List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve");
Predicate<String> startsWithA = s -> s.startsWith("A"); Function<String, String> toUpperCase = String::toUpperCase;
List<String> result = names.stream() .filter(startsWithA) .map(toUpperCase) .collect(Collectors.toList());
System.out.println(result); // [ALICE] }}2. Monaden in Java: Optional und CompletableFuture
Eine Monade ist ein Muster, das einen Wert kapselt und Operationen zu seiner Transformation bereitstellt, ohne ihn explizit auspacken zu müssen. In Java werden Monaden durch Optional (zur Behandlung von null) und CompletableFuture (für asynchrone Operationen) implementiert.
Optional als Monade
Die Methoden map(), flatMap() und filter() ermöglichen die verkettete Arbeit mit der möglichen Abwesenheit eines Wertes.
import java.util.Optional;
public class OptionalMonad { public static void main(String[] args) { // Erstellen eines Optional mit einem nicht-leeren Wert Optional<String> optional = Optional.of("Hello");
// Transformationskette String result = optional .filter(s -> s.length() > 3) // filter .m