CodeGym /Kurse /JAVA 25 SELF /Einführung in das Stream API: warum man Streams braucht

Einführung in das Stream API: warum man Streams braucht

JAVA 25 SELF
Level 30 , Lektion 0
Verfügbar

1. Problem des imperativen Ansatzes

Lassen Sie uns mit einer klassischen Aufgabe beginnen. Nehmen wir an, wir haben eine Liste von Benutzern:

List<User> users = ...; // bereits gefüllt

Angenommen, wir müssen die Liste der Namen aller volljährigen Benutzer (ab 18 Jahren) erhalten. Wie haben wir das früher gemacht?

List<String> names = new ArrayList<>();
for (User user : users) {
    if (user.getAge() >= 18) {
        names.add(user.getName());
    }
}

Auf den ersten Blick ist alles einfach, aber achten Sie auf die „technischen“ Schritte: eine neue Ergebnisliste anlegen, alle Elemente durchlaufen, die Bedingung prüfen, das Resultat hinzufügen.

Wenn die Aufgabe komplexer wird – etwa eine alphabetisch sortierte Liste eindeutiger E‑Mail‑Adressen aller Benutzer, deren Namen mit dem Buchstaben „A“ beginnen –, dann wächst der Code schnell an: Filterung, Feldextraktion, Entfernen von Duplikaten, Sortierung, möglicherweise Umwandlungen des E‑Mail‑Formats usw. Am Ende erhalten Sie statt einer kurzen, verständlichen Logik einen Haufen Boilerplate, der schwer zu lesen und zu warten ist.

Nachteile dieses Ansatzes in zwei Worten: viel duplizierter Code, hohe Fehleranfälligkeit (man vergisst z. B. die Sortierung oder behandelt Duplikate falsch) und Schwierigkeiten bei der Kombination von Operationen. Das nennt man den imperativen Stil: Sie beschreiben dem Computer Schritt für Schritt, wie die Aufgabe auszuführen ist, statt was Sie erhalten wollen.

2. Stream API – deklarativer Stil

Mit Java 8 wurde das Stream API eingeführt – ein Werkzeug, das die Arbeit mit Collections im Stil „was gemacht werden soll“ statt „wie es genau zu tun ist“ ermöglicht. Dieser Ansatz heißt deklarativ.

Was ist ein Stream überhaupt?
Es ist keine eigene Collection, sondern eher ein Datenstrom: eine Folge von Elementen, die durch eine Kette von Operationen geht – Filterung, Transformation, Sortierung, Einsammeln des Ergebnisses usw. Ein Stream speichert nichts, er „schleust“ Daten durch eine Pipeline. Operationen lassen sich fast wie LEGO‑Bausteine kombinieren: Kette zusammenstellen und starten.

Beispiel:

List<String> names = users.stream()                // Stream aus der Benutzerliste erzeugen
    .filter(u -> u.getAge() >= 18)                 // nur diejenigen behalten, die mindestens 18 sind 
    .map(User::getName)                            // User → String abbilden (Name nehmen)
    .collect(Collectors.toList());                 // Ergebnis in eine Liste einsammeln

Und das war’s – in einer Zeile haben wir die gesamte Aufgabe beschrieben: „Nimm die Benutzer, filtere nach Alter, hole die Namen, sammle in einer Liste.“ Man muss keine Schleifen von Hand schreiben, keine temporären Variablen anlegen und nicht darauf achten, nichts zu vergessen.

Das Stream API funktioniert wie ein Fabrikfließband: Sie definieren die Bearbeitungsschritte, und die Daten durchlaufen sie selbst. Schön, kompakt und um ein Vielfaches leichter zu lesen.

3. Vorteile des Stream API

  • Kürze und Lesbarkeit. Vergleichen Sie beide Ansätze – imperativ und deklarativ – an derselben Aufgabe. Man sieht sofort, welcher leichter zu lesen und zu warten ist.
  • Einfache Komposition von Operationen. Operationen lassen sich leicht zu einer Kette „zusammenstecken“: filter, map, sorted, collect – und das alles in einer zusammenhängenden Sequenz.
  • Weniger Fehler. Das Stream API befreit von Routine: keine manuelle Verwaltung von Zwischencollections und Schleifen – geringere Chance, einen Schritt zu vergessen oder einen Indexfehler zu machen.
  • Möglichkeit zur Parallelisierung. Die Verarbeitung lässt sich leicht auf mehrere Kerne skalieren, indem man einfach parallelStream() statt stream() aufruft. Details – später.
  • Moderner Programmierstil. Ideen aus der funktionalen Programmierung (Funktionskomposition, keine expliziten Schleifen) erhöhen die Ausdruckskraft und sind am Markt gefragt.

Kurze Geschichte des Stream API

Das Stream API erschien in Java 8 (2014) und war ein qualitativer Sprung für die Sprache. Bis dahin erforderte jede nicht triviale Operation über Collections viel Hilfscode, während andere Plattformen schon deklarative Ansätze anboten (map, filter, reduce usw.).

Seitdem ist das Stream API De‑facto‑Standard für die Verarbeitung von Collections in Java. Wenn Sie modernen Java‑Code schreiben wollen – geht es nicht ohne.

4. Anwendungsgebiete des Stream API

  • Filterung: nur die benötigten Elemente auswählen (z. B. alle Benutzer älter als 18 Jahre).
  • Transformation: ein Feld extrahieren oder ein neues Objekt erstellen (z. B. die Liste der Benutzernamen).
  • Aggregation: Summe, Mittelwert, Anzahl, Maximum/Minimum berechnen.
  • Sortierung: Elemente nach einem gewünschten Kriterium ordnen.
  • Gruppierung: Elemente nach Kategorien aufteilen.
  • Ergebnis einsammeln: in Liste, Set, Map, String usw. sammeln.

Beispiele für Aufgaben:

  • Eine Liste der E‑Mail‑Adressen aller Benutzer erhalten, deren Name mit „A“ beginnt.
  • Das Durchschnittsalter der Benutzer berechnen.
  • Den ersten Benutzer mit einer bestimmten E‑Mail finden.
  • Alle eindeutigen Wohnorte in ein Set sammeln.

5. Nützliche Details

Wie ein Stream funktioniert

[User1] --\
[User2] ---|--> [ filter ] --> [ map ] --> [ collect ] --> List<String>
[User3] --/

1. filter – lässt nur Benutzer durch, die die Bedingung erfüllen.
2. map – transformiert den Benutzer z. B. in eine E‑Mail.
3. collect – sammelt die E‑Mails in die gewünschte Collection.

Syntax: Wie erstellt man einen Stream

  • Aus einer Collection: list.stream() – Stream der Elemente der Collection.
  • Aus einem Array: Arrays.stream(array)
  • Aus einzelnen Werten: Stream.of("a", "b", "c")

6. Typische Fehler beim Umstieg auf das Stream API

Fehler Nr. 1: Versuch, die Collection innerhalb des Streams zu ändern. Das Stream API ist nicht dafür gedacht, die Ausgangscollection zu modifizieren (z. B. sollte man list.remove() nicht im Rumpf von forEach aufrufen). Für Löschungen verwenden Sie zum Beispiel removeIf auf der Collection davor oder danach.

Fehler Nr. 2: Verwechslung von Collection und Stream. Ein Stream ist keine Collection! Ein Stream „durchläuft“ die Elemente einmal und ist danach geschlossen. Wenn Sie die Daten erneut verarbeiten müssen – erstellen Sie einen neuen stream().

Fehler Nr. 3: Zu komplexe Ketten. Machen Sie aus dem Ausdruck kein „Monster“ mit dutzenden Operationen. Wenn die Kette lang wird – teilen Sie sie in mehrere Schritte mit Zwischenvariablen, um Lesbarkeit und Debugging zu verbessern.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION