Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Вивчайте Обробка Виключень у Stream API | Секція
Stream API в Java

bookОбробка Виключень у Stream API

Свайпніть щоб показати меню

Обробка винятків у Stream API вимагає особливого підходу. На відміну від традиційних циклів, де блок try-catch можна розмістити всередині тіла циклу, стріми працюють декларативно, що ускладнює обробку винятків у них.

Якщо виняток не оброблено, це перериває весь процес обробки стріму. У цьому розділі ви дізнаєтеся, як правильно перехоплювати та обробляти винятки у Stream API.

Проблема обробки винятків

Припустимо, у нашому онлайн-магазині є метод getTotal(), який може викидати виняток, якщо дані замовлення пошкоджені або відсутні. Наприклад, замовлення може бути завантажене з бази даних, де загальна сума збережена як null.

class Order {
    private final double total;

    public Order(double total) {
        this.total = total;
    }

    public double getTotal() throws Exception {
        if (total < 0) {
            throw new IllegalArgumentException("Invalid order total");
        }
        return total;
    }
}

Тепер, якщо будь-яке замовлення має загальну суму менше 0, весь процес роботи Stream API завершиться з винятком.

 List<User> premiumUsers = users.stream()
                .filter(User::isActive)
                .filter(user -> user.getOrders().stream()
                        .filter(order -> order.getTotal() >= 10000)
                        .count() >= 3)
                .toList();

Але існує проблема — цей код навіть не запуститься, оскільки ви не обробляєте винятки, які можуть виникнути у методі getTotal(). Тож розглянемо, як можна обробляти винятки у Stream API.

Обробка виключень у Stream API

Оскільки try-catch не можна використовувати безпосередньо всередині лямбда-виразів, існує кілька стратегій обробки виключень у Stream API.

Один із підходів — перехопити виключення безпосередньо всередині map() і замінити його обробленим результатом:

List<User> premiumUsers = users.stream()
        .filter(User::isActive)
        .filter(user -> user.getOrders().stream()
                .mapToDouble(order -> {
                    try {
                        return order.getTotal();
                    } catch (IllegalArgumentException e) {
                        throw new RuntimeException("Error in user's order: " + user, e);
                    }
                })
                .filter(total -> total >= 10000)
                .count() >= 3)
        .toList();

Усередині mapToDouble() перехоплюється виключення і генерується RuntimeException, вказуючи, який саме користувач спричинив проблему. Такий підхід корисний, коли потрібно негайно зупинити виконання та швидко ідентифікувати причину.

Пропуск елементів з помилками

Іноді немає потреби зупиняти весь процес при виникненні помилки — достатньо просто пропустити проблемні елементи. Для цього можна використати filter() з обробкою виключень:

List<User> premiumUsers = users.stream()
        .filter(User::isActive)
        .filter(user -> {
            try {
                return user.getOrders().stream()
                        .mapToDouble(Order::getTotal)
                        .filter(total -> total >= 10000)
                        .count() >= 3;
            } catch (Exception e) {
                return false; // Skip users with problematic orders
            }
        })
        .toList();

Якщо під час виконання mapToDouble(Order::getTotal) виникає помилка, обробка всього потоку замовлень зазвичай зупиняється. Однак блок try-catch всередині filter() запобігає цьому, гарантуючи, що лише проблемний користувач буде виключений з кінцевого списку.

Обробка виключень за допомогою обгортки

Для підвищення надійності коду можна створити метод-обгортку, який дозволяє обробляти виключення всередині лямбда-виразів з автоматичним перехопленням.

Java не дозволяє Function<T, R> генерувати перевірені виключення. Якщо виключення виникає всередині apply(), його потрібно або обробити в межах методу, або обгорнути у RuntimeException, що ускладнює код. Для спрощення цього визначимо власний функціональний інтерфейс:

@FunctionalInterface
interface ThrowingFunction<T, R> {
    R apply(T t) throws Exception;
}

Цей інтерфейс працює аналогічно до Function<T, R>, але дозволяє методу apply() генерувати виключення.

Тепер створимо клас ExceptionWrapper з методом wrap(), який перетворює ThrowingFunction<T, R> на стандартний Function<T, R> і приймає другий параметр, що визначає резервне значення у разі виключення:

class ExceptionWrapper {
    // Wrapper for `Function` to handle exceptions
    public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> function, R defaultValue) {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception e) {
                System.out.println(e.getMessage());
                return defaultValue;
            }
        };
    }
}

Метод wrap() приймає ThrowingFunction<T, R> і перетворює її на стандартну Function<T, R>, забезпечуючи обробку виключень. Якщо виникає помилка, виводиться повідомлення та повертається вказане значення за замовчуванням.

Використання у Stream API

Припустимо, є список користувачів в інтернет-магазині, і потрібно знайти активних користувачів, які мають щонайменше три замовлення на суму понад 10,000. Однак, якщо у замовленні вказана від’ємна сума, виконання потоку не зупиняється — повертається 0 як ознака некоректної ціни.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
package com.example; import java.util.List; import java.util.function.Function; public class Main { public static void main(String[] args) { List<User> users = List.of( new User("Alice", true, List.of(new Order(12000.00), new Order(15000.00), new Order(11000.00))), new User("Bob", true, List.of(new Order(8000.00), new Order(9000.00), new Order(12000.00))), new User("Charlie", false, List.of(new Order(15000.00), new Order(16000.00), new Order(17000.00))), new User("David", true, List.of(new Order(5000.00), new Order(20000.00), new Order(30000.00))), new User("Eve", true, List.of(new Order(null), new Order(10000.00), new Order(10000.00), new Order(12000.00))), new User("Frank", true, List.of(new Order(-5000.00), new Order(10000.00))) // Error: Negative order amount ); List<User> premiumUsers = users.stream() .filter(User::isActive) .filter(user -> user.getOrders().stream() .map(ExceptionWrapper.wrap(Order::getTotal, 0.0)) // Using the wrapper function .filter(total -> total >= 10000) .count() >= 3) .toList(); System.out.println("Premium users: " + premiumUsers); } } // The `Order` class represents a customer's order class Order { private final Double total; public Order(Double total) { this.total = total; } public double getTotal() throws Exception { if (total == null || total < 0) { throw new Exception("Error: Order amount cannot be negative or equal to null!"); } return total; } } // The `User` class represents an online store user 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; } public String getName() { return name; } @Override public String toString() { return "User{name='" + name + "'}"; } } // Functional interface for handling exceptions in `Function` @FunctionalInterface interface ThrowingFunction<T, R> { R apply(T t) throws Exception; } // A helper class with wrapper methods for exception handling class ExceptionWrapper { // A wrapper for `Function` that catches exceptions public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> function, R defaultValue) { return t -> { try { return function.apply(t); } catch (Exception e) { System.out.println(e.getMessage()); return defaultValue; } }; } }

Тепер, якщо замовлення містить від’ємну суму, програма не зупиняється, а просто фіксує помилку та замінює її на 0.0. Це робить обробку даних більш стійкою та практичною для реального використання.

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 1. Розділ 42

Запитати АІ

expand

Запитати АІ

ChatGPT

Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат

Секція 1. Розділ 42
some-alt