Разница между thenApply и thenCompose в Java: примеры использования
Быстрый ответ
Используйте thenApply
для синхронного преобразования результата CompletableFuture
, когда требуются прямые и быстрые изменения.
Возьмите thenCompose
, когда нужно объединить асинхронные вычислительные задачи, особенно когда следующий шаг также генерирует CompletableFuture
.
Примеры:
// Синхронное преобразование результата через thenApply
CompletableFuture<String> greeting =
CompletableFuture.supplyAsync(() -> "Привет").thenApply(name -> name + ", Мир!");
// Асинхронное преобразование результата через thenCompose
CompletableFuture<String> asyncGreeting =
CompletableFuture.supplyAsync(() -> "Привет").thenCompose(name ->
CompletableFuture.supplyAsync(() -> name + ", Асинхронный Мир!"));
Проще говоря, thenApply
работает быстро, а thenCompose
конструирует цепочку асинхронных шагов.
Суть вопроса: thenApply или thenCompose
thenApply
— это аналог map
из Optional
или Stream
. Он позволяет преобразовать результат CompletableFuture
, получая новый CompletableFuture
с изменённым результатом. Это похоже на превращение воды в вино.
thenCompose
соответствует flatMap
. Он позволяет организовывать операции таким образом, чтобы результатом одной функции был другой CompletableFuture
или CompletionStage
, оставляя структуру CompletableFuture
плоской, без вложенности.
В двух словах:
thenApply
: предназначен для синхронных преобразований.thenCompose
: используется для включения последовательных асинхронных шагов.
Когда стоит использовать thenApply и thenCompose
thenApply
: Если вам нужно применить преобразование к результатуCompletableFuture
.thenCompose
: Идеально, когда функция преобразования возвращаетCompletableFuture
, и вы хотите избежать вложенности.
Разница между синхронными и асинхронными опрецией
thenApply
предназначен для быстрых синхронных преобразований и не обрабатывает вложенность.
thenCompose
идеально подходит для асинхронных операций, рекурсивных асинхронных операций или цепочек шагов неопределённой длины.
Практический пример
Представим, что вам нужно получить данные пользователя и затем данные по его заказам:
// Асинхронная задача: получение пользователя и его заказов
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(() -> userService.getUser(userId));
CompletableFuture<List<Order>> ordersFuture = userFuture.thenCompose(user ->
CompletableFuture.supplyAsync(() -> orderService.getOrders(user)));
Синхронная операция с использованием thenApply
:
// Синхронная задача: получение пользователя и формирование его деталей
CompletableFuture<UserDetails> userDetailsFuture = userFuture.thenApply(user ->
userService.getUserDetails(user));
Визуализация
Сравнение thenApply
и thenCompose
с помощью поездов:
🚂 thenApply: Добавляем вагон к поезду, но путь остается прежним.
🚂🔗🛤 thenCompose: Строим новый маршрут, и поезд переходит на него.
Запомните:
thenApply
= 🚂 Меняем груз, но путь остается тем же.thenCompose
= 🚂🛤 Открыт новый путь!
Устранение вложенности future
thenCompose
помогает избежать вложенности и сохранить линейность последовательности будущих операций.
Пример вложенного future:
// Вложенное future создает проблемы
CompletableFuture<CompletableFuture<String>> nestedFuture =
CompletableFuture.supplyAsync(this::getNestedResult).thenApply(name ->
CompletableFuture.supplyAsync(() -> getContinuedResult(name)));
Решение с использованием thenCompose
:
// thenCompose создает плоскую структуру future
CompletableFuture<String> flatFuture =
CompletableFuture.supplyAsync(this::getFlatResult).thenCompose(name ->
CompletableFuture.supplyAsync(() -> getContinuedResult(name)));
Поиск альтернатив
В новых версиях Java появились методы такого типа, как thenCombine
и thenAcceptBoth
, которые позволяют управлять параллельными асинхронными операциями.
Управление сложными рабочими процессами
В сложных сценариях часто используются комбинации thenApply
и thenCompose
:
// Пример сложного асинхронного рабочего процесса
CompletableFuture<String> complexFlow =
CompletableFuture.supplyAsync(() -> "Начало")
.thenApply(syncStep -> syncStep + " Синхронно")
.thenCompose(asyncStep ->
CompletableFuture.supplyAsync(() -> asyncStep + " Асинхронно"))
.thenApply(finalStep -> finalStep + " Конец");
Это демонстрирует комбинированное использование синхронных и асинхронных преобразований для достижения выходного результата.
Полезные материалы
- CompletionStage (Java Platform SE 8): Официальная документация о асинхронных задачах.
- Introduction to CompletableFuture in Java 8 – YouTube: Визуальное представление
CompletableFuture
в видеоформате. - 20 Practical Examples: Java’s CompletableFuture – DZone: Примеры работы с
CompletableFuture
. - JDK 21 Documentation – Home: Актуальная документация Java SE, включая информацию о
CompletableFuture
и смежных классах. - How to call two functions in a let..in.. in SML/NJ – Stack Overflow: Обсуждение поведения функций и аналогии с композицией future.