Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Learn Real-World Examples of Using Stream API | Practical Applications of Stream API
Stream API
course content

Course Content

Stream API

Stream API

1. Fundamentals and Functional Capabilities of Stream API
4. Practical Applications of Stream API

book
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:

java

Main

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
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:

java

Main

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
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.

Everything was clear?

How can we improve it?

Thanks for your feedback!

Section 4. Chapter 1
We're sorry to hear that something went wrong. What happened?
some-alt