Course Content
Stream API
Stream API
Real-World Examples of Using Stream API
Code isn't just about functionality—it's about readability too. Well-structured code is easier to maintain, modify, and extend.
You're a developer tasked with reviewing someone else's code and making it better. In real projects, code is often written in a hurry, copied from different parts of the program, or simply not designed with readability in mind. Your job is not just to understand the code but to improve it—making it cleaner, more concise, and easier to maintain.
Right now, you're doing a code review. You'll analyze a real piece of code, identify its weak points, and refactor it step by step using Stream API.
Getting Started
Imagine you have an online store, and you need to identify active users who have placed at least three orders of $10,000 or more. This will help the marketing team recognize the most valuable customers and offer them personalized deals.
Here's the initial code before refactoring:
Main
package 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 + "'}"; } }
You have two key classes:
Order
represents an order and contains a total
field storing the order amount.
User
represents a store customer with three fields:
name
(user's name);active
(status flag);orders
(list of orders).
Each User
contains a list of Order objects, modeling a real-world scenario where a customer places multiple orders.
In main
, you create a list of users, each with their own set of orders. The program then iterates through this list and checks if a user is active. If not, they're skipped.
Next, the program loops through the user's orders and counts how many are $10,000 or more. If the user has at least three qualifying orders, they are added to the premiumUsers
list.
Once all users have been processed, the program prints out the premium users.
Issues with the Code
- Too many nested loops – makes it harder to read and understand;
- Redundant code – too many manual checks and intermediate variables;
- Lack of declarative style – the code feels like low-level data processing instead of high-level logic.
You'll now refactor this code step by step using Stream API to improve readability, reduce redundancy, and make it more efficient.
Code Refactoring
The first step is to remove the outer if (user.isActive())
and integrate it directly into the Stream API:
Now, the code is more declarative and clearly shows that you're filtering active users. The unnecessary if
condition is gone—the logic is now built directly into the Stream API. However, this is just data preparation, so let's move forward!
Next, let's replace the nested for loop (for (Order order : user.getOrders())
) with a stream()
inside the filter:
By eliminating manual counting, you've made the code cleaner and more readable—now, count()
handles this for us, allowing us to work with the stream without extra variables.
Final Refactored Code
Now, you have a fully refactored solution that solves the task in a declarative and concise way:
Main
package 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 + "'}"; } }
The code is now shorter and clearer because, instead of a series of manual checks, you use a declarative approach—focusing on what needs to be done rather than detailing each step of the process. This eliminates the need for nested loops, making the code easier to read and maintain.
By leveraging Stream API, you seamlessly combine filtering, counting, and data collection into a single stream, making the code more expressive and efficient.
Thanks for your feedback!