В чём разница между 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. Как правильно выполнить несколько параллельных запросов к микросервисам]]