У чому різниця між thenCombine() і thenCompose()
Обидва методи комбінують CompletableFuture, але по-різному:
🟢 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));
Типові помилки
- thenCompose коли потрібен thenCombine: ```java // ❌ Послідовно (повільніше) getUserAsync(userId) .thenCompose(user -> getOrderAsync(userId));
// ✅ Паралельно (швидше)
CompletableFuture
---
## 🔴 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. Як правильно виконати декілька паралельних запитів до мікросервісів]]