Питання 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. Як правильно виконати декілька паралельних запитів до мікросервісів]]