1. Elemente zählen: count()
Wenn Sie mit Sammlungen von Zahlen oder Objekten arbeiten, tritt fast immer die Aufgabe auf, etwas zu zählen: die Anzahl der Elemente, die Summe, den Durchschnitt, das Maximum oder Minimum. Zum Beispiel die Anzahl der Mitarbeitenden in einem Unternehmen, die Summe aller Gehälter, das Durchschnittsalter der Studierenden, das teuerste Produkt, die größte Bestellung. Mit dem Stream-API wurde alles einfacher und ausdrucksstärker.
Wie funktioniert das
Die Methode count() ist ein terminaler Operator der Stream-API, der die Anzahl der Elemente im Stream zurückgibt.
long count = employees.stream().count();
Wenn Sie nur die Elemente zählen möchten, die einer Bedingung entsprechen – verwenden Sie den Filter filter(...):
long richCount = employees.stream()
.filter(e -> e.getSalary() > 100_000)
.count();
Beispiel für unsere Anwendung:
Angenommen, es gibt eine Produktliste:
List<Product> products = List.of(
new Product("Milch", 80),
new Product("Käse", 250),
new Product("Brot", 40)
);
Zählen wir, wie viele Produkte teurer als 100 sind:
long expensiveCount = products.stream()
.filter(p -> p.getPrice() > 100)
.count();
System.out.println("Teure Produkte: " + expensiveCount);
Was ist Optional
In Java gibt es einen speziellen Container – Optional. Das ist ein Wrapper-Objekt, das einen Wert enthalten kann oder leer ist. Es wird in Fällen eingesetzt, in denen ein Ergebnis fehlen kann und dies explizit angezeigt werden soll. Beispielsweise können die Methoden min(), max() und average() ein leeres Ergebnis zurückgeben, wenn die Sammlung leer ist. Anstelle von null wird ein Optional zurückgegeben.
Zentrale Methoden von Optional
- isPresent() / isEmpty() — prüfen, ob ein Wert vorhanden ist.
- orElse(wert) — Wert zurückgeben oder Default, wenn leer.
- orElseThrow() — eine Ausnahme auslösen, wenn leer.
- ifPresent(...) — eine Aktion nur bei vorhandenem Wert ausführen.
Beispiel:
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
if (min.isPresent()) {
System.out.println("Mindestpreis: " + min.getAsInt());
} else {
System.out.println("Liste ist leer");
}
Oder kürzer:
int minValue = min.orElse(-1);
System.out.println("Mindestpreis: " + minValue);
Dieser Ansatz hilft, NullPointerException zu vermeiden und macht den Code sicherer.
2. Summe, Durchschnitt, Minimum und Maximum für numerische Daten
Primitive Streams: IntStream, DoubleStream, LongStream
Für numerische Daten bietet die Stream-API spezielle Streams: IntStream, DoubleStream, LongStream. Sie ermöglichen es, Summe, Durchschnitt, Minimum und Maximum leicht und schnell zu berechnen.
Umwandlung in einen numerischen Stream
Um einen numerischen Stream aus einem Objektstream zu erhalten, verwenden Sie die Methoden mapToInt, mapToDouble, mapToLong.
int sum = products.stream()
.mapToInt(Product::getPrice)
.sum();
System.out.println("Gesamtsumme der Preise: " + sum);
Methoden
- sum() — Summe aller Elemente.
- average() — arithmetischer Mittelwert (gibt OptionalDouble zurück).
- min(), max() — Minimum und Maximum (geben OptionalInt/OptionalDouble/OptionalLong zurück).
Beispiele:
// Summe aller Preise
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();
// Durchschnittspreis
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
// Minimaler und maximaler Preis
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
OptionalInt max = products.stream()
.mapToInt(Product::getPrice)
.max();
Wie erhält man den Wert aus Optional
double average = avg.orElse(0.0); // 0.0, wenn die Sammlung leer ist
int minValue = min.orElse(-1); // -1, wenn die Sammlung leer ist
int maxValue = max.orElse(-1); // -1, wenn die Sammlung leer ist
Programmiererwitz: Eine leere Liste ist kein Fehler, sondern einfach ein Fall, in dem Sie keine Daten haben. Wenn Sie jedoch versuchen, aus einem leeren Optional den Wert mit getAsInt() zu holen, fangen Sie eine NoSuchElementException. Verwenden Sie immer orElse oder isPresent()!
3. Aggregation von Objekten: Collectors.summingInt, averagingInt, maxBy, minBy
Wenn Sie einen Objektstream haben und Werte nach einem Kriterium aggregieren möchten, verwenden Sie spezielle Collector-Methoden aus der Klasse Collectors.
summingInt, averagingInt
int totalSalary = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
double avgSalary = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
minBy, maxBy
Um das Objekt mit dem maximalen/minimalen Feldwert zu finden:
Optional<Employee> richest = employees.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));
Optional<Employee> poorest = employees.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));
Wichtig: das Ergebnis ist ein Optional, weil die Sammlung leer sein kann.
Beispiel
Erstellen wir die Klasse Product:
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
Erstellen wir eine Produktliste:
List<Product> products = List.of(
new Product("Milch", 80),
new Product("Käse", 250),
new Product("Brot", 40)
);
Finden wir das teuerste Produkt:
Optional<Product> maxProduct = products.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));
maxProduct.ifPresent(p -> System.out.println("Das teuerste Produkt: " + p.getName()));
4. Kombination mit Gruppierung
Aggregierende Methoden werden häufig mit Gruppierung kombiniert. Zum Beispiel kann man den Durchschnittspreis der Produkte nach dem Anfangsbuchstaben des Namens ermitteln:
Map<Character, Double> avgPriceByLetter = products.stream()
.collect(Collectors.groupingBy(
p -> p.getName().charAt(0),
Collectors.averagingInt(Product::getPrice)
));
System.out.println(avgPriceByLetter);
Ergebnis:
{B=40.0, K=250.0, M=80.0}
5. Beispiele aus der Praxis
Beispiel 1: Studierende und Punkte
Liste der Studierenden und ihrer Punkte:
public class Student {
private final String name;
private final int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
List<Student> students = List.of(
new Student("Alice", 90),
new Student("Bob", 75),
new Student("John", 100)
);
Wie viele Studierende mit einer Punktzahl über 80?
long count = students.stream()
.filter(s -> s.getScore() > 80)
.count();
System.out.println("Studierende mit Punktzahl > 80: " + count);
Durchschnittliche Punktzahl:
double avg = students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
System.out.println("Durchschnittliche Punktzahl: " + avg);
Der erfolgreichste Studierende:
students.stream()
.max(Comparator.comparingInt(Student::getScore))
.ifPresent(s -> System.out.println("Bester Student: " + s.getName()));
Beispiel 2: Anzahl einzigartiger Produkte
long uniqueCount = products.stream()
.map(Product::getName)
.distinct()
.count();
System.out.println("Einzigartige Produkte: " + uniqueCount);
6. Nützliche Hinweise
Visuelles Schema: wie Aggregationsmethoden funktionieren
Sammlung von Objekten
│
▼
stream()
│
▼
mapToInt/Double/Long (falls nötig)
│
▼
sum() / average() / min() / max() / count()
│
▼
Ergebnis (int, double, long, Optional)
Umgang mit Optional: wie man Werte sicher entnimmt
Warum geben Methoden ein Optional zurück?
Wenn die Sammlung leer ist (z. B. Sie suchen das Maximum in einer leeren Liste), wird ein leeres Optional zurückgegeben. Rufen Sie sofort getAsInt()/get() auf, wird eine Ausnahme geworfen.
So geht’s richtig:
- orElse(Standardwert) verwenden
- ifPresent(...) verwenden, um Aktionen nur bei vorhandenem Wert auszuführen
- orElseThrow(...) verwenden, um explizit eine Ausnahme zu werfen
Beispiel:
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
System.out.println("Durchschnittspreis: " + avg.orElse(0.0));
Wann primitive Streams und wann – Collectors
- Wenn Sie einfach Summe/Durchschnitt/Minimum/Maximum über ein numerisches Feld berechnen möchten – verwenden Sie mapToInt und die entsprechende Methode (sum, average, min, max).
- Wenn Sie Objekte aggregieren möchten (z. B. das Objekt mit dem maximalen Feld finden), verwenden Sie die Collector-Methoden maxBy/minBy.
- Wenn Sie gruppieren und Aggregate pro Gruppe berechnen müssen – verwenden Sie eine Kombination aus groupingBy mit aggregierenden Collectors.
7. Typische Fehler beim Arbeiten mit Aggregationsmethoden
Fehler Nr. 1: Optional nicht behandelt. Anfänger vergessen oft, dass min, max, average ein Optional zurückgeben. Beim Versuch, den Wert aus einem leeren Container zu holen, entsteht eine NoSuchElementException. Verwenden Sie immer orElse, orElseThrow oder ifPresent.
Fehler Nr. 2: Verwendung eines normalen Stream statt mapToInt/mapToDouble. Wenn Sie stream().sum() schreiben, sagt der Compiler: „Diese Methode gibt es nicht!“. Für Summe, Durchschnitt, Minimum und Maximum sind primitive Streams erforderlich.
Fehler Nr. 3: Typinkompatibilitäten beim Vergleichen. Wenn Sie Maximum/Minimum unter Objekten suchen, verwenden Sie den richtigen Comparator: Comparator.comparingInt(Employee::getSalary). Andernfalls kann es zu einem Compilerfehler oder einem falschen Ergebnis kommen.
Fehler Nr. 4: Vergleich von Objekten mit null. Wenn in der Sammlung null-Elemente vorkommen können, kann die Aggregation eine NullPointerException werfen. Filtern Sie solche Elemente vorher heraus oder verwenden Sie Comparator.nullsLast(...)/nullsFirst(...).
Fehler Nr. 5: Verwendung von sum/average/min/max mit leeren Sammlungen ohne Beachtung des Ergebnisses. Ist die Sammlung leer, gibt sum() 0 zurück, während average()/min()/max() ein leeres Optional liefern. Vergessen Sie nicht, diesen Fall zu behandeln.
GO TO FULL VERSION