CodeGym /Kurse /JAVA 25 SELF /Methoden reduce und collect: Datenaggregation

Methoden reduce und collect: Datenaggregation

JAVA 25 SELF
Level 31, Lektion 1
Verfügbar

1. Die Methode reduce: universelle Faltung

In der Programmierung muss man oft eine Sammlung auf einen einzigen Endwert „zusammenfalten“: eine Summe bilden, ein Produkt berechnen, Strings verketten, eine aggregierte Metrik ermitteln oder Elemente in eine neue Struktur überführen. Früher geschah das manuell mit Schleifen und einer Akkumulatorvariablen. Heute bietet die Stream-API elegante Wege – die universellen Methoden reduce und collect, mit denen sich kompakter und deklarativer Code schreiben lässt.

  • reduce – faltet den Stream zu einem einzigen Endwert (Summe, Produkt, Konkatenation usw.).
  • collect – transformiert den Stream in eine Sammlung, einen String, eine Map oder eine beliebige Struktur.

Gehen wir der Reihe nach vor.

Wozu braucht man reduce?

reduce ist eine terminale Methode, die die Elemente eines Streams mithilfe einer Akkumulatorfunktion zu einem einzigen Wert „zusammenfaltet“. Gedanklich entspricht das einem Durchlauf über die Sammlung mit schrittweiser Ergebnisakkumulation.

Signaturen der Methode reduce

In der Stream-API gibt es drei Hauptvarianten von reduce():

Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
  • accumulator – Funktion, die den aktuell akkumulierten Wert und das nächste Element entgegennimmt und ein neues Ergebnis zurückgibt.
  • identity – Startwert des Akkumulators (z. B. 0 für Summen, 1 für Produkte).
  • combiner – wird in parallelen Streams verwendet, um Zwischenergebnisse zu kombinieren.

Beispiele für die Verwendung von reduce

Beispiel 1: Summe der Zahlen

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

// reduce ohne identity - Ergebnis: Optional
Optional<Integer> sum1 = numbers.stream()
    .reduce((a, b) -> a + b);

System.out.println(sum1.orElse(0)); // 15

// reduce mit identity - Ergebnis ist immer vorhanden
int sum2 = numbers.stream()
    .reduce(0, (a, b) -> a + b);

System.out.println(sum2); // 15

Beispiel 2: Produkt aller Zahlen

int product = numbers.stream()
    .reduce(1, (a, b) -> a * b);
System.out.println(product); // 120

Beispiel 3: Verkettung von Strings

List<String> words = List.of("Java", "Stream", "API");
String phrase = words.stream()
    .reduce("", (a, b) -> a + " " + b);
System.out.println(phrase.trim()); // Java Stream API

Beispiel 4: Suche nach dem Maximum

Optional<Integer> max = numbers.stream()
    .reduce(Integer::max);
max.ifPresent(System.out::println); // 5

Beispiel 5: Summe der Längen aller Strings

List<String> texts = List.of("Katze", "Hund", "Elefant");
int totalLength = texts.stream()
    .map(String::length)
    .reduce(0, Integer::sum);
System.out.println(totalLength); // 16

Wie reduce funktioniert

Die Logik von reduce entspricht folgender Schleife:

T result = identity;
for (T element : collection) {
    result = accumulator.apply(result, element);
}
return result;

Wenn kein identity angegeben ist, wird der erste Stream-Wert als Startwert genommen, und die Methode liefert ein Optional zurück (kann leer sein, wenn der Stream leer ist).

2. Die Methode collect: universelle Umwandlung

collect ist eine terminale Methode, die einen Stream in eine Sammlung, einen String, eine Map oder eine andere Struktur überführt. Dafür werden „Collector“ (Collector) verwendet, die den Sammelprozess beschreiben. Meist nimmt man die fertigen aus der Klasse Collectors.

Die beliebtesten Collector

  • Collectors.toList() – sammelt Elemente in eine List.
  • Collectors.toSet() – sammelt Elemente in ein Set.
  • Collectors.toMap() – sammelt Elemente in eine Map.
  • Collectors.joining() – führt Strings zu einem zusammen.
  • Collectors.groupingBy() – gruppiert Elemente nach einem Kriterium.
  • Collectors.counting() – zählt die Anzahl der Elemente.
  • Collectors.summarizingInt() – liefert Statistiken über Zahlen (Summe, Durchschnitt, Min/Max).

Beispiele für collect

Beispiel 1: In eine Liste sammeln

List<String> names = List.of("Alice", "Bob", "Charlie", "Alice");
List<String> uniqueNames = names.stream()
    .distinct()
    .collect(Collectors.toList());
System.out.println(uniqueNames); // [Alice, Bob, Charlie]

Beispiel 2: In eine Menge (Set) sammeln

Set<String> nameSet = names.stream()
    .collect(Collectors.toSet());
System.out.println(nameSet); // [Alice, Bob, Charlie] (Reihenfolge nicht garantiert)

Beispiel 3: In einen String sammeln

String csv = names.stream()
    .collect(Collectors.joining(", "));
System.out.println(csv); // Alice, Bob, Charlie, Alice

Beispiel 4: In eine Map sammeln

Angenommen, wir haben eine Klasse:

public class Employee {
    private String name;
    private String department;
    public Employee(String name, String department) {
        this.name = name;
        this.department = department;
    }
    public String getName() { return name; }
    public String getDepartment() { return department; }
}

Wir bauen eine Map „Name → Abteilung“:

List<Employee> employees = List.of(
    new Employee("Alice", "IT"),
    new Employee("Bob", "HR"),
    new Employee("Charlie", "IT")
);

Map<String, String> nameToDept = employees.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        Employee::getDepartment,
        (oldValue, newValue) -> newValue // Behandlung doppelter Namen
    ));

System.out.println(nameToDept); // {Alice=IT, Bob=HR, Charlie=IT}

Beispiel 5: Eindeutige Elemente in ein Set sammeln

Set<String> unique = names.stream()
    .collect(Collectors.toSet());
System.out.println(unique);

Beispiel 6: Statistiken über Zahlen sammeln

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
IntSummaryStatistics stats = numbers.stream()
    .collect(Collectors.summarizingInt(Integer::intValue));
System.out.println(stats.getSum());     // 15
System.out.println(stats.getAverage()); // 3.0
System.out.println(stats.getMax());     // 5
System.out.println(stats.getMin());     // 1

3. Vergleich: Wann reduce verwenden und wann collect

reduce – wenn mit einer binären Operation ein einzelner Endwert benötigt wird: Summe, Produkt, Maximum, Konkatenation.

collect – wenn Elemente in eine Sammlung/Map/einen String gesammelt oder eine komplexere Aggregation mit einem Collector durchgeführt werden soll. Für solche Aufgaben ist collect in der Regel mächtiger und effizienter.

Tabelle: reduce vs. collect

Aufgabe Was verwenden Beispiel
Summe der Zahlen
reduce
reduce(0, Integer::sum)
Produkt
reduce
reduce(1, (a, b) -> a * b)
In eine List sammeln
collect
collect(Collectors.toList())
In eine Map sammeln
collect
collect(Collectors.toMap(...))
Gruppierung
collect
collect(Collectors.groupingBy(...))
Stringverkettung reduce / collect reduce("", String::concat) oder Collectors.joining()

4. Praxisaufgaben

Aufgabe 1: Summe der Längen aller Strings in der Liste berechnen

List<String> words = List.of("Katze", "Hund", "Elefant");

int totalLength = words.stream()
    .mapToInt(String::length)
    .sum(); // oder via reduce: .reduce(0, Integer::sum)

System.out.println(totalLength); // 16

Aufgabe 2: Eindeutige Elemente in ein Set sammeln

List<String> fruits = List.of("Apfel", "Birne", "Apfel", "Orange");

Set<String> uniqueFruits = fruits.stream()
    .collect(Collectors.toSet());

System.out.println(uniqueFruits); // [Apfel, Birne, Orange]

Aufgabe 3: Eine Map aus einer Objektliste erstellen

List<Employee> employees = List.of(
    new Employee("Alice", "IT"),
    new Employee("Bob", "HR"),
    new Employee("Charlie", "IT")
);

Map<String, String> nameToDept = employees.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        Employee::getDepartment,
        (oldValue, newValue) -> newValue // wenn die Namen übereinstimmen
    ));

System.out.println(nameToDept);

Aufgabe 4: Alle Namen durch Komma getrennt zusammenfügen

String allNames = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.joining(", "));

System.out.println(allNames); // Alice, Bob, Charlie

5. Implementierungsbesonderheiten und Feinheiten

Optional und reduce

Wenn man reduce ohne identity verwendet, ist das Ergebnis ein Optional. Das ist sicher: Ist der Stream leer, ist auch das Ergebnis leer. Vergessen Sie nicht, es korrekt zu behandeln: ifPresent(...), orElse(...), orElseThrow(...).

Optional<Integer> max = numbers.stream().reduce(Integer::max);
max.ifPresent(System.out::println);

Eigene Collector: wenn es unbedingt sein muss

Sie können Ihren eigenen Collector schreiben, wenn die Standardvarianten nicht ausreichen. Für 99% der Aufgaben genügen jedoch die fertigen aus Collectors.

Collector und parallele Streams

Die vordefinierten Collector aus Collectors sind für die korrekte Arbeit mit parallelStream() ausgelegt. Fügen Sie in einem parallelen Stream nicht manuell Elemente in eine gemeinsame veränderliche Sammlung innerhalb von forEach ein – sonst bekommen Sie Race Conditions.

6. Häufige Fehler im Umgang mit reduce und collect

Fehler Nr. 1: Optional nach reduce nicht prüfen. Wenn der Stream leer ist, liefert reduce ohne identity ein leeres Optional zurück. Ein Aufruf von get() führt zu einer NoSuchElementException. Verwenden Sie ifPresent, orElse oder orElseThrow.

Fehler Nr. 2: Eine Sammlung per reduce aufbauen. Das ist möglich, aber collect ist dafür besser und schneller geeignet:

// Ineffizient!
List<String> list = stream.reduce(
    new ArrayList<>(),
    (acc, elem) -> { acc.add(elem); return acc; },
    (acc1, acc2) -> { acc1.addAll(acc2); return acc1; }
);
// Besser so:
List<String> list2 = stream.collect(Collectors.toList());

Fehler Nr. 3: Doppelte Schlüssel in toMap nicht behandeln. Wenn Schlüssel kollidieren, gibt es eine Ausnahme. Fügen Sie das dritte Argument in toMap hinzu, um Konflikte aufzulösen.

Fehler Nr. 4: Änderbare Sammlungen in parallelen Streams ohne Synchronisation verwenden. Verwenden Sie in collect die Standard-Collector – sie funktionieren im Parallelbetrieb korrekt. Rufen Sie nicht list.add() in forEach auf einem parallelStream() auf.

Fehler Nr. 5: reduce und collect bei komplexen Aufgaben verwechseln. reduce – für einfache Aggregationen (Summe, Maximum). collect – für das Sammeln in Sammlungen, Gruppierung, den Aufbau von Map und komplexe Aggregationen.

1
Aufgabe
JAVA 25 SELF, Level 31, Lektion 1
Gesperrt
Suche der Wochenhöchsttemperatur 🌡️
Suche der Wochenhöchsttemperatur 🌡️
1
Aufgabe
JAVA 25 SELF, Level 31, Lektion 1
Gesperrt
Erstellung eines magischen Zauberspruchs 📜
Erstellung eines magischen Zauberspruchs 📜
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION