Вопрос 23 · Раздел 19

В чём разница между thenCombine() и thenCompose()

Оба метода комбинируют CompletableFuture, но по-разному:

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Оба метода комбинируют CompletableFuture, но по-разному:

  • thenCombine() — ждёт ДВА независимых CF и объединяет их результаты
  • thenCompose() — ждёт ОДИН CF и запускает другой CF на основе его результата
// thenCombine — два CF, объединение результатов
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> "World");

cf1.thenCombine(cf2, (s1, s2) -> s1 + " " + s2)
   .thenAccept(System.out::println);  // Hello World

// thenCompose — один CF, потом другой
CompletableFuture<String> cf = getUserIdAsync();
cf.thenCompose(userId -> getUserAsync(userId))
  .thenAccept(user -> System.out.println(user.name()));

🟡 Middle Level

thenCombine — параллельное выполнение

CompletableFuture<User> user = getUserAsync(userId);
CompletableFuture<Order> order = getOrderAsync(userId);

// Оба выполняются параллельно
user.thenCombine(order, (u, o) -> new UserProfile(u, o))
    .thenAccept(profile -> System.out.println(profile));

thenCompose — последовательное выполнение

// Сначала getUserId, потом getUser
getUserIdAsync()
    .thenCompose(userId -> getUserAsync(userId))  // последовательно
    .thenAccept(user -> System.out.println(user));

Типичные ошибки

  1. thenCompose когда нужен thenCombine: ```java // ❌ Последовательно (медленнее) getUserAsync(userId) .thenCompose(user -> getOrderAsync(userId));

// ✅ Параллельно (быстрее) CompletableFuture user = getUserAsync(userId); CompletableFuture order = getOrderAsync(userId); user.thenCombine(order, (u, o) -> new Summary(u, o));


---

## 🔴 Senior Level

### Internal Implementation

**thenCombine:**
```java
public <U,V> CompletableFuture<V> thenCombine(
    CompletionStage<? extends U> other,
    BiFunction<? super T,? super U,? extends V> fn
) {
    return biApplyStage(null, other, fn);
}

// Упрощённое представление внутренних методов JDK. Реальные методы имеют
// другие сигнатуры и дополнительные параметры. Концептуально:
// biApplyStage — создаёт CF, который ждёт ОБА результата и применяет функцию.

thenCompose:

public <U> CompletableFuture<U> thenCompose(
    Function<? super T, ? extends CompletionStage<U>> fn
) {
    return uniComposeStage(null, fn);
}

// uniComposeStage — создаёт CF, который ждёт первый результат и запускает вторую операцию.

Performance

thenCombine:
- Оба CF параллельно
- Latency = max(cf1, cf2)
- thenCompose:
- CF последовательно
- Latency = cf1 + cf2

Production Experience

// ✅ thenCombine для параллельных вызовов
CompletableFuture<User> user = getUserAsync(userId);
CompletableFuture<Order> order = getOrderAsync(userId);
user.thenCombine(order, UserProfile::new);

// ✅ thenCompose для зависимых вызовов
getUserIdAsync()
    .thenCompose(userId -> getUserAsync(userId));

Best Practices

// ✅ thenCombine для независимых CF
cf1.thenCombine(cf2, combiner);

// ✅ thenCompose для зависимых CF
cf.thenCompose(result -> nextAsync(result));

// ❌ thenCompose для независимых (медленнее)
// ❌ thenCompose когда CF не зависит от результата

🎯 Шпаргалка для интервью

Обязательно знать:

  • thenCombine — два НЕЗАВИСИМЫХ CF, параллельное выполнение, latency = max(cf1, cf2)
  • thenCompose — ЗАВИСИМЫЕ CF, последовательное выполнение, latency = cf1 + cf2
  • thenCombine принимает BiFunction<T, U, V>, thenCompose принимает Function<T, CompletionStage>
  • thenCompose аналог flatMap, thenCombine аналог zip
  • Частая ошибка: thenCompose для независимых CF — unnecessarily slow

Частые уточняющие вопросы:

  • thenCombine для зависимых CF? — Нет, thenCombine не может использовать результат первого во втором
  • thenCompose для независимых? — Работает, но последовательно (медленнее). Лучше thenCombine
  • thenCombine vs allOf? — thenCombine для двух CF с combiner, allOf для N CF (возвращает Void)
  • Internal implementation? — thenCombine: BiRelay ждёт оба CF. thenCompose: uniComposeStage ждёт первый, запускает второй

Красные флаги (НЕ говорить):

  • «thenCombine и thenCompose взаимозаменяемы» — разная семантика: параллельно vs последовательно
  • «thenCompose быстрее» — thenCompose последовательный (sum latency), thenCombine параллельный (max latency)
  • «thenCombine принимает Function» — принимает BiFunction от результатов двух CF

Связанные темы:

  • [[4. В чём разница между thenApply() и thenCompose()]]
  • [[8. Как комбинировать результаты нескольких CompletableFuture]]
  • [[9. Что делает метод allOf() и когда его использовать]]
  • [[16. Как правильно выполнить несколько параллельных запросов к микросервисам]]