Einführung in die funktionale Programmierung in Java
Lange Zeit wurde Java ausschließlich mit objektorientierter Programmierung (OOP) assoziiert. Mit der Veröffentlichung von Java 8 im Jahr 2014 machte die Sprache jedoch einen gewaltigen Schritt in Richtung des funktionalen Paradigmas. Lambda-Ausdrücke, funktionale Interfaces und die Stream API haben die Herangehensweise an das Schreiben von Code grundlegend verändert und ihn prägnanter, lesbarer und weniger fehleranfällig gemacht.
Funktionale Programmierung in Java ist kein Ersatz für OOP, sondern eine leistungsstarke Ergänzung. Sie ermöglicht es, deklarativen Code zu schreiben, bei dem Sie beschreiben, was getan werden soll, und nicht wie. In diesem Artikel werden wir die wichtigsten Konzepte behandeln, die Ihnen helfen, den funktionalen Stil in Java zu meistern, und praktische Beispiele zeigen.
1. Lambda-Ausdrücke und funktionale Interfaces
Die Grundlage der funktionalen Programmierung in Java sind Lambda-Ausdrücke – eine kompakte Möglichkeit, anonyme Funktionen zu implementieren. Syntaktisch besteht ein Lambda aus einer Parameterliste, einem Pfeil (->) und einem Rumpf:
(Parameter) -> { Rumpf }Lambdas arbeiten immer paarweise mit funktionalen Interfaces. Ein funktionales Interface ist ein Interface, das genau eine abstrakte Methode enthält (z. B. Runnable, Comparator, Predicate).
Praktisches Beispiel: Ersetzen einer anonymen Klasse
Betrachten wir den Vergleich zwischen dem alten (OOP) und dem neuen (funktionalen) Ansatz:
// Alter Weg (anonyme Klasse)Runnable taskOld = new Runnable() { @Override public void run() { System.out.println("Hallo aus der anonymen Klasse!"); }};
// Neuer Weg (Lambda)Runnable taskNew = () -> System.out.println("Hallo aus dem Lambda!");
// AusführungtaskNew.run();Wie Sie sehen, hat das Lambda den Code von 6 Zeilen auf 1 reduziert, während die volle Funktionalität erhalten blieb.
Eingebaute funktionale Interfaces (Paket java.util.function)
Java bietet eine umfangreiche Sammlung eingebauter Interfaces. Hier sind die vier wichtigsten:
Predicate<T>– nimmt ein Objekt entgegen, gibtbooleanzurück. Wird zum Filtern verwendet.Function<T, R>– nimmt ein Objekt eines Typs entgegen, gibt ein Objekt eines anderen Typs zurück. Für Transformationen.Consumer<T>– nimmt ein Objekt entgegen, gibt nichts zurück (void). Für Seiteneffekte (z. B. Ausgabe auf der Konsole).Supplier<T>– nimmt nichts entgegen, gibt ein Objekt zurück. Für die verzögerte Generierung von Daten.
Beispiel mit Predicate:
import java.util.function.Predicate;
public class Main { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; System.out.println(isEven.test(10)); // true System.out.println(isEven.test(7)); // false }}2. Stream API: Arbeiten mit Sammlungen im funktionalen Stil
Die Stream API ist das Juwel der funktionalen Programmierung in Java. Ein Stream ist eine Sequenz von Elementen, die sequentielle und parallele Operationen unterstützt. Der Hauptvorteil: Sie verändern die ursprünglichen Daten nicht, sondern erzeugen ein neues Ergebnis.
Die Stream API basiert auf drei Phasen:
- Erstellen eines Streams (aus einer Sammlung, einem Array oder einem Generator).
- Zwischenoperationen (Filtern, Sortieren, Transformieren). Geben einen neuen Stream zurück.
- Terminale Operation (Sammeln des Ergebnisses, Zählen, Suchen). Beendet die Arbeit des Streams.
Beispiel: Filtern und Transformieren einer Liste
Angenommen, wir haben eine Liste von Zeichenketten und möchten nur diejenigen, die länger als 3 Zeichen sind, in Großbuchstaben umwandeln und sortieren.
import java.u