CodeGym /Kurse /JAVA 25 SELF /Grundlegende Operationen des Stream API: map, filter, col...

Grundlegende Operationen des Stream API: map, filter, collect

JAVA 25 SELF
Level 30, Lektion 1
Verfügbar

1. Einen Stream erstellen

Um das Stream API zu verwenden, muss man zuerst einen Stream aus einer Collection oder einem Array erzeugen.

Beispiele zur Erstellung eines Streams

// Aus einer Liste
List<String> names = List.of("Anna", "Boris", "Alex", "Alina");
Stream<String> stream = names.stream();

// Aus einem Array
int[] numbers = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(numbers);

// Aus einzelnen Werten
Stream<String> letters = Stream.of("A", "B", "C");

Kurz:

  • list.stream() — für Collections
  • Arrays.stream(array) — für Arrays
  • Stream.of(...) — für einzelne Werte

Beispiel im Kontext unserer Anwendung

Nehmen wir an, wir haben eine Liste von Benutzern:

List<String> users = List.of("John", "Anna", "Peter", "Alex");
Stream<String> userStream = users.stream();

Zwischen- und Terminaloperationen

Wichtig: Die Operationen im Stream API lassen sich in zwei Typen einteilen.

  • Zwischenoperationen (z. B. filter, map, distinct) — beschreiben die Verarbeitungsschritte. Sie geben einen neuen Stream zurück, stoßen für sich genommen jedoch nichts an.
  • Terminaloperationen (z. B. collect, forEach, count) — starten die Pipeline und liefern ein Ergebnis.

Ein Stream arbeitet „lazy“: Solange keine Terminaloperation aufgerufen wird, passiert keine Berechnung. Deshalb beenden wir eine Kette oft mit collect(...) — das ist der Punkt, an dem der Stream wieder in eine Collection oder ein anderes Ergebnis überführt wird.

2. Operation filter: Elemente nach einer Bedingung filtern

filter ist eine Zwischenoperation, die nur diejenigen Elemente passieren lässt, die der vorgegebenen Bedingung entsprechen.

Signatur

Stream<T> filter(Predicate<? super T> predicate); 

Predicate ist ein funktionales Interface, das ein Element entgegennimmt und true (behalten) oder false (aussortieren) zurückgibt.

Beispiel: nur gerade Zahlen behalten

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);

List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

System.out.println(evenNumbers); // [2, 4, 6]

Was passiert hier?

  • n -> n % 2 == 0 — ein Lambda-Ausdruck, der prüft, ob die Zahl ohne Rest durch 2 teilbar ist.
  • filter behält nur die geraden Zahlen.

Beispiel: Namen filtern, die mit „A“ beginnen

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "John");

List<String> aNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

System.out.println(aNames); // [Anna, Alex, Alina]

Wichtig: filter verändert die Collection nicht — es wird ein neuer Stream erzeugt, der nur die benötigten Elemente enthält.

3. Operation map: ein Element in etwas anderes transformieren

map ist eine Transformationsoperation. Sie nimmt jedes Element des Streams, wendet darauf eine Funktion an und gibt ein neues Element zurück.

Signatur

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

Function ist ein Interface, das ein Element annimmt und etwas zurückgibt (möglicherweise eines anderen Typs).

Beispiel: die Längen der Strings ermitteln

List<String> names = List.of("Anna", "Boris", "Alex");

List<Integer> nameLengths = names.stream()
    .map(name -> name.length())
    .collect(Collectors.toList());

System.out.println(nameLengths); // [4, 5, 4]

Was passiert hier?

  • map verwandelt den String in seine Länge (name -> name.length()).
  • Ergebnis ist ein Stream von Zahlen.

Beispiel: Strings in Großbuchstaben umwandeln

List<String> names = List.of("Anna", "Boris", "Alex");

List<String> upperNames = names.stream()
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());

System.out.println(upperNames); // [ANNA, BORIS, ALEX]

4. Operation collect: Ergebnis zurück in eine Collection sammeln

collect ist eine Terminaloperation, d. h. sie beendet die Arbeit des Streams und sammelt das Ergebnis in einer Collection oder einem anderen Container.

Signatur

<R, A> R collect(Collector<? super T, A, R> collector)

Keine Angst vor der „furchteinflößenden“ Signatur! In 99 % der Fälle verwenden Sie fertige Collector-Implementierungen aus der Klasse Collectors.

Collectors ist eine Utility-Klasse mit einer Sammlung von „Sammlern“. Sie sagt dem Stream, in welche Form das Ergebnis gesammelt werden soll: Liste, Menge, String usw.

Beispiele:

  • Collectors.toList() — in eine List
  • Collectors.toSet() — in ein Set
  • Collectors.joining(", ") — in einen String, durch Kommas getrennt

Mit anderen Worten: Collectors ist wie ein Satz unterschiedlich geformter Schachteln, in die Sie die Stream-Elemente verpacken.

Beispiel: Ergebnis in eine List sammeln

List<String> filtered = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());

Beispiel: Ergebnis in ein Set sammeln

Set<String> uniqueNames = names.stream()
    .map(String::toLowerCase)
    .collect(Collectors.toSet());

Beispiel: Strings mit Komma verketten

String result = names.stream()
    .collect(Collectors.joining(", "));

System.out.println(result); // Anna, Boris, Alex

5. Operationskette: Filtern + Transformieren + Ergebnis sammeln

Die größte Stärke des Stream API — ist die Möglichkeit, Operationen aneinanderzuketten.

Beispiel: Längen der Namen ermitteln, die mit „A“ beginnen

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "John");

List<Integer> aNameLengths = names.stream()
    .filter(name -> name.startsWith("A"))
    .map(String::length)
    .collect(Collectors.toList());

System.out.println(aNameLengths); // [4, 4, 5]

Schritt für Schritt:

  1. .stream() — wir erzeugen einen Stream aus der Liste.
  2. .filter(name -> name.startsWith("A")) — wir behalten nur Namen, die mit "A" beginnen.
  3. .map(String::length) — wir wandeln jeden Namen in seine Länge um.
  4. .collect(Collectors.toList()) — wir sammeln das Ergebnis in einer Liste.

Äquivalenter imperativer Code

So würde dasselbe „auf die alte Art“ aussehen:

List<Integer> result = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("A")) {
        result.add(name.length());
    }
}

Vergleich: Stream API — eine Zeile, und es liest sich wie „was wir tun“ statt „wie wir es tun“.

6. Praxis: einige kurze Aufgaben

Üben wir! Alle Beispiele können in einer einzigen Datei ausgeführt werden — ändern Sie einfach die Daten.

Aufgabe 1: Nur ungerade Zahlen behalten und quadrieren

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);

List<Integer> oddSquares = numbers.stream()
    .filter(n -> n % 2 != 0)
    .map(n -> n * n)
    .collect(Collectors.toList());

System.out.println(oddSquares); // [1, 9, 25, 49]

Aufgabe 2: Aus einer Liste von Strings die Liste ihrer Anfangsbuchstaben erzeugen

List<String> names = List.of("Anna", "Boris", "Alex");

List<Character> initials = names.stream()
    .map(name -> name.charAt(0))
    .collect(Collectors.toList());

System.out.println(initials); // [A, B, A]

Aufgabe 3: Strings mit Länge größer als 3 filtern und in ein Set sammeln

List<String> words = List.of("cat", "dog", "elephant", "ant", "bear");

Set<String> longWords = words.stream()
    .filter(word -> word.length() > 3)
    .collect(Collectors.toSet());

System.out.println(longWords); // [bear, elephant]

7. Typische Fehler im Umgang mit filter, map, collect

Fehler Nr. 1: collect vergessen — kein Ergebnis!
Das Stream API ist träge wie eine Katze auf dem Fensterbrett: Solange Sie keine Terminaloperation (z. B. collect oder forEach) aufrufen, passiert nichts. Wenn Sie nur users.stream().filter(...).map(...); schreiben, wird nichts ausgeführt.

Fehler Nr. 2: filter und map vertauscht
Manche Einsteiger machen zuerst map und dann filter. Zum Beispiel, names.stream().map(String::length).filter(len -> len > 3) — das liefert Zahlen statt Strings. Wenn Sie Strings mit Länge größer als 3 brauchen, filtern Sie zuerst und transformieren danach.

Fehler Nr. 3: Unveränderlichkeit vergessen
Operationen des Stream API ändern die ursprüngliche Collection nicht! Sie liefern ein neues Ergebnis. Nach List<String> upper = names.stream().map(String::toUpperCase).collect(Collectors.toList()); — bleibt die Collection names unverändert.

Fehler Nr. 4: Versuch, eine externe veränderliche Liste zu verwenden
So sollte man es nicht machen:

List<String> result = new ArrayList<>();
names.stream().filter(...).forEach(name -> result.add(name));

Besser collect verwenden — das ist sicherer und kürzer.

Fehler Nr. 5: NullPointerException
Wenn eine Collection null-Elemente enthalten kann, führt der Aufruf name.startsWith("A") auf null zu einem Fehler. Fügen Sie, wenn möglich, einen Null-Filter hinzu:

.filter(name -> name != null && name.startsWith("A"))
1
Aufgabe
JAVA 25 SELF, Level 30, Lektion 1
Gesperrt
Inventar im Zauberladen 🍎
Inventar im Zauberladen 🍎
1
Aufgabe
JAVA 25 SELF, Level 30, Lektion 1
Gesperrt
Sensorüberwachung im Labor 🧪
Sensorüberwachung im Labor 🧪
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION