Praxisbeispiele für die Verwendung der Stream-API
Code ist nicht nur eine Frage der Funktionalität – auch die Lesbarkeit spielt eine wichtige Rolle. Gut strukturierter Code lässt sich leichter warten, anpassen und erweitern.
Sie sind Entwickler und mit der Überprüfung des Codes einer anderen Person beauftragt, um diesen zu verbessern. In realen Projekten wird Code häufig eilig geschrieben, aus verschiedenen Teilen des Programms kopiert oder ohne Rücksicht auf Lesbarkeit entworfen. Ihre Aufgabe besteht nicht nur darin, den Code zu verstehen, sondern ihn auch zu optimieren – also übersichtlicher, prägnanter und leichter wartbar zu machen.
Aktuell führen Sie ein Code-Review durch. Sie werden einen echten Codeausschnitt analysieren, dessen Schwachstellen identifizieren und ihn Schritt für Schritt mithilfe der Stream API refaktorisieren.
Einstieg
Stellen Sie sich vor, Sie betreiben einen Online-Shop und müssen aktive Nutzer identifizieren, die mindestens drei Bestellungen im Wert von jeweils $10.000 oder mehr aufgegeben haben. Dies hilft dem Marketing-Team, die wertvollsten Kunden zu erkennen und ihnen personalisierte Angebote zu unterbreiten.
Hier ist der Ausgangscode vor dem Refactoring:
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970package com.example; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000), new Order(15000), new Order(11000))), new User("Bob", true, List.of(new Order(8000), new Order(9000), new Order(12000))), new User("Charlie", false, List.of(new Order(15000), new Order(16000), new Order(17000))), new User("David", true, List.of(new Order(5000), new Order(20000), new Order(30000))), new User("Eve", true, List.of(new Order(10000), new Order(10000), new Order(10000), new Order(12000))) ); List<User> premiumUsers = new ArrayList<>(); for (User user : users) { if (user.isActive()) { int totalOrders = 0; for (Order order : user.getOrders()) { if (order.getTotal() >= 10000) { totalOrders++; } } if (totalOrders >= 3) { premiumUsers.add(user); } } } System.out.println("Premium users: " + premiumUsers); } } class Order { private final double total; public Order(double total) { this.total = total; } public double getTotal() { return total; } } class User { private final String name; private final boolean active; private final List<Order> orders; public User(String name, boolean active, List<Order> orders) { this.name = name; this.active = active; this.orders = orders; } public boolean isActive() { return active; } public List<Order> getOrders() { return orders; } @Override public String toString() { return "User{name='" + name + "'}"; } }
Es gibt zwei Schlüsselklassen:
Order steht für eine Bestellung und enthält ein Feld total, das den Bestellbetrag speichert.
User steht für einen Kunden des Geschäfts mit drei Feldern:
name(Name des Benutzers);active(Status-Flag);orders(Liste der Bestellungen).
Jeder User enthält eine Liste von Order-Objekten und bildet damit ein reales Szenario ab, in dem ein Kunde mehrere Bestellungen aufgibt.
In main wird eine Liste von Benutzern erstellt, jeweils mit einem eigenen Satz von Bestellungen. Das Programm durchläuft dann diese Liste und prüft, ob ein Benutzer aktiv ist. Ist dies nicht der Fall, wird er übersprungen.
Anschließend durchläuft das Programm die Bestellungen des Benutzers und zählt, wie viele davon 10.000 $ oder mehr betragen. Hat der Benutzer mindestens drei qualifizierende Bestellungen, wird er zur Liste premiumUsers hinzugefügt.
Nachdem alle Benutzer verarbeitet wurden, gibt das Programm die Premium-Benutzer aus.
Probleme mit dem Code
- Zu viele verschachtelte Schleifen – erschwert das Lesen und Verstehen;
- Redundanter Code – zu viele manuelle Prüfungen und Zwischenvariablen;
- Fehlender deklarativer Stil – der Code wirkt wie eine Low-Level-Datenverarbeitung statt wie eine High-Level-Logik.
Im Folgenden wird dieser Code Schritt für Schritt mit der Stream API überarbeitet, um die Lesbarkeit zu verbessern, Redundanz zu reduzieren und ihn effizienter zu gestalten.
Code-Refaktorisierung
Der erste Schritt besteht darin, die äußere if (user.isActive())-Bedingung zu entfernen und sie direkt in die Stream API zu integrieren:
List<User> premiumUsers = users.stream()
.filter(User::isActive) // Keep only active users
.toList();
Jetzt ist der Code deutlich deklarativer und zeigt klar, dass aktive Benutzer gefiltert werden. Die unnötige if-Bedingung entfällt—die Logik ist nun direkt in der Stream API abgebildet. Dies ist jedoch nur die Datenvorbereitung, daher gehen wir weiter!
Als Nächstes wird die verschachtelte for-Schleife (for (Order order : user.getOrders())) durch einen stream() innerhalb des Filters ersetzt:
List<User> premiumUsers = users.stream()
.filter(User::isActive)
.filter(user -> user.getOrders().stream()
.filter(order -> order.getTotal() >= 10000)
.count() >= 3) // Count orders directly in Stream API
.toList();
Durch das Entfernen der manuellen Zählung wird der Code übersichtlicher und lesbarer—nun übernimmt count() diese Aufgabe, sodass mit dem Stream ohne zusätzliche Variablen gearbeitet werden kann.
Endgültig überarbeiteter Code
Nun steht eine vollständig überarbeitete Lösung zur Verfügung, die die Aufgabe auf deklarative und prägnante Weise löst:
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061package com.example; import java.util.List; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000), new Order(15000), new Order(11000))), new User("Bob", true, List.of(new Order(8000), new Order(9000), new Order(12000))), new User("Charlie", false, List.of(new Order(15000), new Order(16000), new Order(17000))), new User("David", true, List.of(new Order(5000), new Order(20000), new Order(30000))), new User("Eve", true, List.of(new Order(10000), new Order(10000), new Order(10000), new Order(12000))) ); List<User> premiumUsers = users.stream() .filter(User::isActive) .filter(user -> user.getOrders().stream() .filter(order -> order.getTotal() >= 10000) .count() >= 3) .toList(); System.out.println("Premium users: " + premiumUsers); } } class Order { private final double total; public Order(double total) { this.total = total; } public double getTotal() { return total; } } class User { private final String name; private final boolean active; private final List<Order> orders; public User(String name, boolean active, List<Order> orders) { this.name = name; this.active = active; this.orders = orders; } public boolean isActive() { return active; } public List<Order> getOrders() { return orders; } @Override public String toString() { return "User{name='" + name + "'}"; } }
Der Code ist jetzt kürzer und übersichtlicher, da anstelle einer Reihe von manuellen Prüfungen ein deklarativer Ansatz verwendet wird—der Fokus liegt darauf, was getan werden soll, anstatt jeden einzelnen Schritt des Prozesses zu beschreiben. Dadurch entfällt die Notwendigkeit für verschachtelte Schleifen, was den Code leichter lesbar und wartbar macht.
Durch die Nutzung der Stream-API werden Filtern, Zählen und Datensammlung nahtlos in einem einzigen Stream kombiniert, wodurch der Code ausdrucksstärker und effizienter wird.
Danke für Ihr Feedback!
Fragen Sie AI
Fragen Sie AI
Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen
Awesome!
Completion rate improved to 2.33
Praxisbeispiele für die Verwendung der Stream-API
Swipe um das Menü anzuzeigen
Code ist nicht nur eine Frage der Funktionalität – auch die Lesbarkeit spielt eine wichtige Rolle. Gut strukturierter Code lässt sich leichter warten, anpassen und erweitern.
Sie sind Entwickler und mit der Überprüfung des Codes einer anderen Person beauftragt, um diesen zu verbessern. In realen Projekten wird Code häufig eilig geschrieben, aus verschiedenen Teilen des Programms kopiert oder ohne Rücksicht auf Lesbarkeit entworfen. Ihre Aufgabe besteht nicht nur darin, den Code zu verstehen, sondern ihn auch zu optimieren – also übersichtlicher, prägnanter und leichter wartbar zu machen.
Aktuell führen Sie ein Code-Review durch. Sie werden einen echten Codeausschnitt analysieren, dessen Schwachstellen identifizieren und ihn Schritt für Schritt mithilfe der Stream API refaktorisieren.
Einstieg
Stellen Sie sich vor, Sie betreiben einen Online-Shop und müssen aktive Nutzer identifizieren, die mindestens drei Bestellungen im Wert von jeweils $10.000 oder mehr aufgegeben haben. Dies hilft dem Marketing-Team, die wertvollsten Kunden zu erkennen und ihnen personalisierte Angebote zu unterbreiten.
Hier ist der Ausgangscode vor dem Refactoring:
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970package com.example; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000), new Order(15000), new Order(11000))), new User("Bob", true, List.of(new Order(8000), new Order(9000), new Order(12000))), new User("Charlie", false, List.of(new Order(15000), new Order(16000), new Order(17000))), new User("David", true, List.of(new Order(5000), new Order(20000), new Order(30000))), new User("Eve", true, List.of(new Order(10000), new Order(10000), new Order(10000), new Order(12000))) ); List<User> premiumUsers = new ArrayList<>(); for (User user : users) { if (user.isActive()) { int totalOrders = 0; for (Order order : user.getOrders()) { if (order.getTotal() >= 10000) { totalOrders++; } } if (totalOrders >= 3) { premiumUsers.add(user); } } } System.out.println("Premium users: " + premiumUsers); } } class Order { private final double total; public Order(double total) { this.total = total; } public double getTotal() { return total; } } class User { private final String name; private final boolean active; private final List<Order> orders; public User(String name, boolean active, List<Order> orders) { this.name = name; this.active = active; this.orders = orders; } public boolean isActive() { return active; } public List<Order> getOrders() { return orders; } @Override public String toString() { return "User{name='" + name + "'}"; } }
Es gibt zwei Schlüsselklassen:
Order steht für eine Bestellung und enthält ein Feld total, das den Bestellbetrag speichert.
User steht für einen Kunden des Geschäfts mit drei Feldern:
name(Name des Benutzers);active(Status-Flag);orders(Liste der Bestellungen).
Jeder User enthält eine Liste von Order-Objekten und bildet damit ein reales Szenario ab, in dem ein Kunde mehrere Bestellungen aufgibt.
In main wird eine Liste von Benutzern erstellt, jeweils mit einem eigenen Satz von Bestellungen. Das Programm durchläuft dann diese Liste und prüft, ob ein Benutzer aktiv ist. Ist dies nicht der Fall, wird er übersprungen.
Anschließend durchläuft das Programm die Bestellungen des Benutzers und zählt, wie viele davon 10.000 $ oder mehr betragen. Hat der Benutzer mindestens drei qualifizierende Bestellungen, wird er zur Liste premiumUsers hinzugefügt.
Nachdem alle Benutzer verarbeitet wurden, gibt das Programm die Premium-Benutzer aus.
Probleme mit dem Code
- Zu viele verschachtelte Schleifen – erschwert das Lesen und Verstehen;
- Redundanter Code – zu viele manuelle Prüfungen und Zwischenvariablen;
- Fehlender deklarativer Stil – der Code wirkt wie eine Low-Level-Datenverarbeitung statt wie eine High-Level-Logik.
Im Folgenden wird dieser Code Schritt für Schritt mit der Stream API überarbeitet, um die Lesbarkeit zu verbessern, Redundanz zu reduzieren und ihn effizienter zu gestalten.
Code-Refaktorisierung
Der erste Schritt besteht darin, die äußere if (user.isActive())-Bedingung zu entfernen und sie direkt in die Stream API zu integrieren:
List<User> premiumUsers = users.stream()
.filter(User::isActive) // Keep only active users
.toList();
Jetzt ist der Code deutlich deklarativer und zeigt klar, dass aktive Benutzer gefiltert werden. Die unnötige if-Bedingung entfällt—die Logik ist nun direkt in der Stream API abgebildet. Dies ist jedoch nur die Datenvorbereitung, daher gehen wir weiter!
Als Nächstes wird die verschachtelte for-Schleife (for (Order order : user.getOrders())) durch einen stream() innerhalb des Filters ersetzt:
List<User> premiumUsers = users.stream()
.filter(User::isActive)
.filter(user -> user.getOrders().stream()
.filter(order -> order.getTotal() >= 10000)
.count() >= 3) // Count orders directly in Stream API
.toList();
Durch das Entfernen der manuellen Zählung wird der Code übersichtlicher und lesbarer—nun übernimmt count() diese Aufgabe, sodass mit dem Stream ohne zusätzliche Variablen gearbeitet werden kann.
Endgültig überarbeiteter Code
Nun steht eine vollständig überarbeitete Lösung zur Verfügung, die die Aufgabe auf deklarative und prägnante Weise löst:
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061package com.example; import java.util.List; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000), new Order(15000), new Order(11000))), new User("Bob", true, List.of(new Order(8000), new Order(9000), new Order(12000))), new User("Charlie", false, List.of(new Order(15000), new Order(16000), new Order(17000))), new User("David", true, List.of(new Order(5000), new Order(20000), new Order(30000))), new User("Eve", true, List.of(new Order(10000), new Order(10000), new Order(10000), new Order(12000))) ); List<User> premiumUsers = users.stream() .filter(User::isActive) .filter(user -> user.getOrders().stream() .filter(order -> order.getTotal() >= 10000) .count() >= 3) .toList(); System.out.println("Premium users: " + premiumUsers); } } class Order { private final double total; public Order(double total) { this.total = total; } public double getTotal() { return total; } } class User { private final String name; private final boolean active; private final List<Order> orders; public User(String name, boolean active, List<Order> orders) { this.name = name; this.active = active; this.orders = orders; } public boolean isActive() { return active; } public List<Order> getOrders() { return orders; } @Override public String toString() { return "User{name='" + name + "'}"; } }
Der Code ist jetzt kürzer und übersichtlicher, da anstelle einer Reihe von manuellen Prüfungen ein deklarativer Ansatz verwendet wird—der Fokus liegt darauf, was getan werden soll, anstatt jeden einzelnen Schritt des Prozesses zu beschreiben. Dadurch entfällt die Notwendigkeit für verschachtelte Schleifen, was den Code leichter lesbar und wartbar macht.
Durch die Nutzung der Stream-API werden Filtern, Zählen und Datensammlung nahtlos in einem einzigen Stream kombiniert, wodurch der Code ausdrucksstärker und effizienter wird.
Danke für Ihr Feedback!