logo

Разница между thenApply и thenCompose в Java: примеры использования

Быстрый ответ

Используйте thenApply для синхронного преобразования результата CompletableFuture, когда требуются прямые и быстрые изменения.

Возьмите thenCompose, когда нужно объединить асинхронные вычислительные задачи, особенно когда следующий шаг также генерирует CompletableFuture.

Примеры:

Java
Скопировать код
// Синхронное преобразование результата через 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 идеально подходит для асинхронных операций, рекурсивных асинхронных операций или цепочек шагов неопределённой длины.

Практический пример

Представим, что вам нужно получить данные пользователя и затем данные по его заказам:

Java
Скопировать код
// Асинхронная задача: получение пользователя и его заказов
CompletableFuture<User> userFuture = 
    CompletableFuture.supplyAsync(() -> userService.getUser(userId));

CompletableFuture<List<Order>> ordersFuture = userFuture.thenCompose(user -> 
    CompletableFuture.supplyAsync(() -> orderService.getOrders(user)));

Синхронная операция с использованием thenApply:

Java
Скопировать код
// Синхронная задача: получение пользователя и формирование его деталей
CompletableFuture<UserDetails> userDetailsFuture = userFuture.thenApply(user -> 
    userService.getUserDetails(user));

Визуализация

Сравнение thenApply и thenCompose с помощью поездов:

Markdown
Скопировать код
🚂 thenApply: Добавляем вагон к поезду, но путь остается прежним.
Markdown
Скопировать код
🚂🔗🛤 thenCompose: Строим новый маршрут, и поезд переходит на него.

Запомните:

  • thenApply = 🚂 Меняем груз, но путь остается тем же.
  • thenCompose = 🚂🛤 Открыт новый путь!

Устранение вложенности future

thenCompose помогает избежать вложенности и сохранить линейность последовательности будущих операций.

Пример вложенного future:

Java
Скопировать код
// Вложенное future создает проблемы
CompletableFuture<CompletableFuture<String>> nestedFuture = 
    CompletableFuture.supplyAsync(this::getNestedResult).thenApply(name -> 
        CompletableFuture.supplyAsync(() -> getContinuedResult(name)));

Решение с использованием thenCompose:

Java
Скопировать код
// thenCompose создает плоскую структуру future
CompletableFuture<String> flatFuture = 
    CompletableFuture.supplyAsync(this::getFlatResult).thenCompose(name -> 
        CompletableFuture.supplyAsync(() -> getContinuedResult(name)));

Поиск альтернатив

В новых версиях Java появились методы такого типа, как thenCombine и thenAcceptBoth, которые позволяют управлять параллельными асинхронными операциями.

Управление сложными рабочими процессами

В сложных сценариях часто используются комбинации thenApply и thenCompose:

Java
Скопировать код
// Пример сложного асинхронного рабочего процесса
CompletableFuture<String> complexFlow = 
    CompletableFuture.supplyAsync(() -> "Начало")
        .thenApply(syncStep -> syncStep + " Синхронно")
        .thenCompose(asyncStep -> 
            CompletableFuture.supplyAsync(() -> asyncStep + " Асинхронно"))
        .thenApply(finalStep -> finalStep + " Конец");

Это демонстрирует комбинированное использование синхронных и асинхронных преобразований для достижения выходного результата.

Полезные материалы

  1. CompletionStage (Java Platform SE 8): Официальная документация о асинхронных задачах.
  2. Introduction to CompletableFuture in Java 8 – YouTube: Визуальное представление CompletableFuture в видеоформате.
  3. 20 Practical Examples: Java’s CompletableFuture – DZone: Примеры работы с CompletableFuture.
  4. JDK 21 Documentation – Home: Актуальная документация Java SE, включая информацию о CompletableFuture и смежных классах.
  5. How to call two functions in a let..in.. in SML/NJ – Stack Overflow: Обсуждение поведения функций и аналогии с композицией future.