Обробка Виключень у 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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394package 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; } }; } }
Тепер, якщо order містить negative amount, program не зупиняється, а просто фіксує error та замінює його на 0.0. Це робить data processing більш robust та practical для real-world use.
Дякуємо за ваш відгук!
Запитати АІ
Запитати АІ
Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат
Awesome!
Completion rate improved to 2.33
Обробка Виключень у 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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394package 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; } }; } }
Тепер, якщо order містить negative amount, program не зупиняється, а просто фіксує error та замінює його на 0.0. Це робить data processing більш robust та practical для real-world use.
Дякуємо за ваш відгук!