Приклади Використання Stream API у Реальних Задачах
Код — це не лише про функціональність, а й про читабельність. Добре структурований код легше підтримувати, модифікувати та розширювати.
Ви — розробник, якому доручено перевірити чужий код і зробити його кращим. У реальних проєктах код часто пишуть поспіхом, копіюють з різних частин програми або просто не дбають про читабельність. Ваше завдання — не лише зрозуміти код, а й покращити його: зробити чистішим, більш лаконічним і простішим для підтримки.
Зараз ви проводите code review. Ви будете аналізувати реальний фрагмент коду, визначати його слабкі місця та рефакторити його крок за кроком із використанням Stream API.
Початок роботи
Уявіть, що у вас є інтернет-магазин, і вам потрібно визначити активних користувачів, які зробили щонайменше три замовлення на суму $10,000 або більше. Це допоможе маркетинговій команді визначити найбільш цінних клієнтів і запропонувати їм персоналізовані пропозиції.
Ось початковий код до рефакторингу:
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 + "'}"; } }
У вас є два ключові класи:
Order представляє замовлення та містить поле total, яке зберігає суму замовлення.
User представляє клієнта магазину з трьома полями:
name(ім'я користувача);active(прапорець статусу);orders(список замовлень).
Кожен User містить список об'єктів Order, що моделює реальну ситуацію, коли клієнт робить кілька замовлень.
У main створюється список користувачів, кожен з власним набором замовлень. Програма далі перебирає цей список і перевіряє, чи користувач активний. Якщо ні — його пропускають.
Далі програма перебирає замовлення користувача та рахує, скільки з них на $10,000 або більше. Якщо у користувача є щонайменше три таких замовлення, його додають до списку premiumUsers.
Після обробки всіх користувачів програма виводить преміум-користувачів.
Проблеми з кодом
- Занадто багато вкладених циклів – ускладнює читання та розуміння;
- Надлишковий код – забагато ручних перевірок і проміжних змінних;
- Відсутність декларативного стилю – код виглядає як низькорівнева обробка даних замість високорівневої логіки.
Тепер ви будете рефакторити цей код крок за кроком, використовуючи Stream API, щоб покращити читабельність, зменшити надлишковість і зробити його більш ефективним.
Рефакторинг коду
Перший крок — видалити зовнішній if (user.isActive()) та інтегрувати цю перевірку безпосередньо у Stream API:
List<User> premiumUsers = users.stream()
.filter(User::isActive) // Keep only active users
.toList();
Тепер код став більш декларативним і чітко показує, що відбувається фільтрація активних користувачів. Зайва умова if зникла — логіка тепер реалізована безпосередньо у Stream API. Проте це лише підготовка даних, тож рухаємося далі!
Далі слід замінити вкладений цикл for (for (Order order : user.getOrders())) на використання stream() всередині filter:
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();
Завдяки усуненню ручного підрахунку код став чистішим і більш читабельним — тепер за це відповідає count(), що дозволяє працювати зі стрімом без додаткових змінних.
Фінальний рефакторинг коду
Тепер ви маєте повністю рефакторене рішення, яке вирішує завдання у декларативній та лаконічній формі:
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 + "'}"; } }
Код став коротшим і зрозумілішим, оскільки замість низки ручних перевірок використовується декларативний підхід—зосередження на тому, що потрібно зробити, а не на деталізації кожного кроку процесу. Це усуває необхідність у вкладених циклах, роблячи код легшим для читання та супроводу.
Завдяки Stream API можна безперервно поєднувати фільтрацію, підрахунок і збір даних в одному потоці, що робить код більш виразним і ефективним.
Дякуємо за ваш відгук!
Запитати АІ
Запитати АІ
Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат
Can you explain how the Stream API improves code readability in this example?
What are some potential drawbacks of using the Stream API in this scenario?
Can you show how the original (non-refactored) code looked for comparison?
Awesome!
Completion rate improved to 2.33
Приклади Використання Stream API у Реальних Задачах
Свайпніть щоб показати меню
Код — це не лише про функціональність, а й про читабельність. Добре структурований код легше підтримувати, модифікувати та розширювати.
Ви — розробник, якому доручено перевірити чужий код і зробити його кращим. У реальних проєктах код часто пишуть поспіхом, копіюють з різних частин програми або просто не дбають про читабельність. Ваше завдання — не лише зрозуміти код, а й покращити його: зробити чистішим, більш лаконічним і простішим для підтримки.
Зараз ви проводите code review. Ви будете аналізувати реальний фрагмент коду, визначати його слабкі місця та рефакторити його крок за кроком із використанням Stream API.
Початок роботи
Уявіть, що у вас є інтернет-магазин, і вам потрібно визначити активних користувачів, які зробили щонайменше три замовлення на суму $10,000 або більше. Це допоможе маркетинговій команді визначити найбільш цінних клієнтів і запропонувати їм персоналізовані пропозиції.
Ось початковий код до рефакторингу:
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 + "'}"; } }
У вас є два ключові класи:
Order представляє замовлення та містить поле total, яке зберігає суму замовлення.
User представляє клієнта магазину з трьома полями:
name(ім'я користувача);active(прапорець статусу);orders(список замовлень).
Кожен User містить список об'єктів Order, що моделює реальну ситуацію, коли клієнт робить кілька замовлень.
У main створюється список користувачів, кожен з власним набором замовлень. Програма далі перебирає цей список і перевіряє, чи користувач активний. Якщо ні — його пропускають.
Далі програма перебирає замовлення користувача та рахує, скільки з них на $10,000 або більше. Якщо у користувача є щонайменше три таких замовлення, його додають до списку premiumUsers.
Після обробки всіх користувачів програма виводить преміум-користувачів.
Проблеми з кодом
- Занадто багато вкладених циклів – ускладнює читання та розуміння;
- Надлишковий код – забагато ручних перевірок і проміжних змінних;
- Відсутність декларативного стилю – код виглядає як низькорівнева обробка даних замість високорівневої логіки.
Тепер ви будете рефакторити цей код крок за кроком, використовуючи Stream API, щоб покращити читабельність, зменшити надлишковість і зробити його більш ефективним.
Рефакторинг коду
Перший крок — видалити зовнішній if (user.isActive()) та інтегрувати цю перевірку безпосередньо у Stream API:
List<User> premiumUsers = users.stream()
.filter(User::isActive) // Keep only active users
.toList();
Тепер код став більш декларативним і чітко показує, що відбувається фільтрація активних користувачів. Зайва умова if зникла — логіка тепер реалізована безпосередньо у Stream API. Проте це лише підготовка даних, тож рухаємося далі!
Далі слід замінити вкладений цикл for (for (Order order : user.getOrders())) на використання stream() всередині filter:
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();
Завдяки усуненню ручного підрахунку код став чистішим і більш читабельним — тепер за це відповідає count(), що дозволяє працювати зі стрімом без додаткових змінних.
Фінальний рефакторинг коду
Тепер ви маєте повністю рефакторене рішення, яке вирішує завдання у декларативній та лаконічній формі:
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 + "'}"; } }
Код став коротшим і зрозумілішим, оскільки замість низки ручних перевірок використовується декларативний підхід—зосередження на тому, що потрібно зробити, а не на деталізації кожного кроку процесу. Це усуває необхідність у вкладених циклах, роблячи код легшим для читання та супроводу.
Завдяки Stream API можна безперервно поєднувати фільтрацію, підрахунок і збір даних в одному потоці, що робить код більш виразним і ефективним.
Дякуємо за ваш відгук!