
Привет, Хабр!
Асинхронное программирование уже давно является полноценной частью Java. С появлением Java 8 и введением класса CompletableFuture, асинхронное программирование стало более доступным.
CompletableFuture — это класс в пакете java.util.concurrent, предоставляющий возможности для асинхронного программирования. Он поддерживает выполнение задач в фоновом режиме, цепочки задач, обработку исключений и многое другое.
Основные методы CompletableFuture
Метод supplyAsync() используется для асинхронного выполнения задачи, возвращающей результат. Задача выполняется в фоновом потоке, предоставляемом ForkJoinPool.commonPool(), если не указан другой Executor:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // Ввыполнение задачи try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Результат"; }); future.thenAccept(result -> { // обработка результата System.out.println("Получен результат: " + result); });
thenApply() используется для обработки результата CompletableFuture и возвращает новый CompletableFuture с преобразованным результатом:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!"); CompletableFuture<String> transformedFuture = future.thenApply(result -> result.toUpperCase()); transformedFuture.thenAccept(result -> { System.out.println("Преобразованный результат: " + result); });
thenAccept() принимает результат CompletableFuture и выполняет действие, не возвращая нового значения. Хорош для случаев, когда нужно просто обработать результат:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!"); future.thenAccept(result -> { System.out.println("Результат: " + result); });
thenRun() выполняет указанное действие после завершения CompletableFuture, не используя его результат. Это хорошо подходит для логирования или освобождения ресурсов.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!"); future.thenRun(() -> { System.out.println("Задача завершена!"); });
Комбинирование и цепочки
Можно объединять и связывать различные задачи.
thenCompose() используется для объединения двух зависимых задач. Когда первая задача завершится, thenCompose() используется для запуска следующей задачи, используя результат первой. Это предотвращает некоторую вложенность.
Например, получение информации о юзере и его кредит рейтинга:
CompletableFuture<User> getUserDetail(String userId) { return CompletableFuture.supplyAsync(() -> { // запрос к удаленному сервису для получения данных пользователя return UserService.getUserDetails(userId); }); } CompletableFuture<Double> getCreditRating(User user) { return CompletableFuture.supplyAsync(() -> { // запрос к другому сервису для получения кредитного рейтинга return CreditRatingService.getCreditRating(user); }); } CompletableFuture<Double> result = getUserDetail("123") .thenCompose(user -> getCreditRating(user)); result.thenAccept(rating -> { System.out.println("Кредитный рейтинг: " + rating); });
thenCompose() используется для последовательного выполнения задач: сначала получение данных пользователя, затем — кредитного рейтинга на основе полученных данных пользователя.
thenCombine() используется для объединения результатов двух независимых задач. Он запускает обе задачи параллельно и комбинирует их результаты, когда обе задачи завершены.
Например, расчет CompletableFuture<Double> weightInKgFuture = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new IllegalStateException(e); } return 65.0; }); CompletableFuture<Double> heightInCmFuture = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new IllegalStateException(e); } return 175.0; }); CompletableFuture<Double> bmiFuture = weightInKgFuture.thenCombine(heightInCmFuture, (weight, height) -> { double heightInMeters = height / 100; return weight / (heightInMeters * heightInMeters); }); bmiFuture.thenAccept(bmi -> { System.out.println("Индекс массы тела: " + bmi); });
Паттерн Fan-Out/Fan-In позволяет выполнять несколько задач параллельно и затем объединять их результаты. Это можно реализовать с помощью методов В этом примере обе задачи запускаются параллельно, и Метод Если задача завершится с ошибкой, Метод Метод Если задача не завершится в течение 1 секунды, она будет завершена со значением «Тайм-аут». Подробнее с класс можно ознакомиться здесь. Записывайтесь на открытые уроки в рамках курса Java для начинающих: 5 июня. Введение в Stream API: посмотрим, как изменилась Java, начиная с 8-й версии. На практике создадим программы на языке Java и интерпретируем базовый вариант решения задач, но уже с применением Stream API. Записаться 18 июня. Сборка приложения на Java: рассмотрим, как запустить собрать исполняемый jar-файл, добавить ресурсы в него и запустить java-приложение. ЗаписатьсяthenCombine() объединяет результаты двух асинхронных задач: получение веса и роста, чтобы рассчитать BMI.allOf() и anyOf().allOf() позволяет запускать несколько задач параллельно и ждать их завершения:CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> { // задача 1 System.out.println("Задача 1 выполнена"); }); CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> { // задача 2 System.out.println("Задача 2 выполнена"); }); CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2); combinedFuture.thenRun(() -> { System.out.println("Все задачи выполнены"); });thenRun() выполняется после их завершения.Обработка ошибок и тайм-ауты в CompletableFuture
exceptionally() позволяет обработать исключение и вернуть значение, которое заменит результат завершившегося с ошибкой CompletableFuture. Метод вызывается только в случае исключения и не активируется при успешном завершении задачи:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { if (new Random().nextBoolean()) { throw new RuntimeException("Что-то пошло не так"); } return "Успех!"; }).exceptionally(ex -> { System.out.println("Обработка исключения: " + ex.getMessage()); return "Восстановлено после ошибки"; }); future.thenAccept(result -> System.out.println("Результат: " + result));exceptionally() обработает исключение и вернет строку «Восстановлено после ошибки».handle() позволяет обработать как успешный результат, так и исключение, используя BiFunction, принимающий результат или исключение в качестве аргументов. Он всегда выполняется независимо от того, была ли ошибка:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { if (new Random().nextBoolean()) { throw new RuntimeException("Что-то пошло не так"); } return "Успех!"; }).handle((result, ex) -> { if (ex != null) { System.out.println("Обработка исключения: " + ex.getMessage()); return "Восстановлено после ошибки"; } return result; }); future.thenAccept(result -> System.out.println("Результат: " + result));handle() обработает как результат, так и исключение, возвращая соответствующее значение в зависимости от исхода задачи.completeOnTimeout() позволяет установить значение, которое будет возвращено, если задача не завершится в течение указанного времени:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "Успех!"; }).completeOnTimeout("Тайм-аут", 1, TimeUnit.SECONDS); future.thenAccept(result -> System.out.println("Результат: " + result));
ссылка на оригинал статьи https://habr.com/ru/articles/818955/
Добавить комментарий