Что делает метод allOf() и когда его использовать
Structured Java interview answer with junior, middle, and senior-level explanation.
🟢 Junior Level
allOf() — ждёт завершения ВСЕХ переданных CompletableFuture и возвращает новый CompletableFuture.
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(() -> "!");
// Ждёт завершения всех трёх
CompletableFuture<Void> all = CompletableFuture.allOf(cf1, cf2, cf3);
all.thenRun(() -> {
// Все завершены — можно получать результаты
System.out.println(cf1.join() + " " + cf2.join() + " " + cf3.join());
// Hello World !
});
Важно: allOf() возвращает CompletableFuture<Void> — не содержит результаты, нужно вызывать join() на каждом исходном CF.
🟡 Middle Level
Как это работает
CompletableFuture<User> user = getUserAsync(userId);
CompletableFuture<Order> order = getOrderAsync(userId);
CompletableFuture<Preferences> prefs = getPrefsAsync(userId);
// Ждём все
CompletableFuture<Void> all = CompletableFuture.allOf(user, order, prefs);
// После завершения всех
all.thenRun(() -> {
User u = user.join(); // не блокирует — уже готов
Order o = order.join();
Preferences p = prefs.join();
process(new UserProfile(u, o, p));
});
Обработка ошибок
// Если хотя бы один упадёт — allOf тоже упадёт
CompletableFuture.allOf(cf1, cf2, cf3)
.exceptionally(ex -> {
// ex — CompletionException с причиной
log.error("One of the CFs failed", ex);
return null;
});
Типичные ошибки
- Попытка получить результаты из allOf:
```java
// ❌ allOf возвращает Void!
CompletableFuture
all = CompletableFuture.allOf(cf1, cf2); // all.join(); // null!
// ✅ Нужно join на каждом CF CompletableFuture.allOf(cf1, cf2).thenRun(() -> { String r1 = cf1.join(); String r2 = cf2.join(); });
---
## 🔴 Senior Level
### Internal Implementation
```java
// Внутренне allOf создаёт дерево зависимостей и завершается,
// когда все переданные CF завершены. Реальные имена внутренних
// классов в JDK отличаются — это концептуальное описание.
Architetural Trade-offs
| allOf() | thenCombine() |
|---|---|
| Много CF (2+) | Два CF |
| Возвращает Void | Возвращает результат |
| Нужно join для результатов | Результаты в callback |
Edge Cases
1. Collect results pattern:
public CompletableFuture<List<User>> getUsers(List<Long> ids) {
List<CompletableFuture<User>> futures = ids.stream()
.map(id -> getUserAsync(id))
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v ->
futures.stream()
.map(CompletableFuture::join)
.toList()
);
}
2. Partial results with timeout:
CompletableFuture<Void> all = CompletableFuture.allOf(cf1, cf2, cf3);
all.orTimeout(5, TimeUnit.SECONDS)
.exceptionally(ex -> {
// Timeout или ошибка
// Но некоторые CF могли завершиться
if (cf1.isDone()) System.out.println("cf1 done");
if (cf2.isDone()) System.out.println("cf2 done");
return null;
});
Производительность
allOf():
- Создание: ~10 ns
- Overhead на каждый CF: ~5 ns
- При N CF — дерево глубины log(N)
join() на готовом CF: ~1 ns (не блокирует)
Production Experience
Batch API calls:
@Service
public class DashboardService {
public CompletableFuture<Dashboard> getDashboard(Long userId) {
CompletableFuture<User> user = userService.findByIdAsync(userId);
CompletableFuture<List<Order>> orders = orderService.findByUserIdAsync(userId);
CompletableFuture<List<Notification>> notifications =
notificationService.findByUserIdAsync(userId);
CompletableFuture<Stats> stats = statsService.getStatsAsync(userId);
return CompletableFuture.allOf(user, orders, notifications, stats)
.thenApply(v -> new Dashboard(
user.join(),
orders.join(),
notifications.join(),
stats.join()
));
}
}
Best Practices
// ✅ allOf для параллельных независимых вызовов
CompletableFuture.allOf(cf1, cf2, cf3)
.thenRun(() -> combine(cf1.join(), cf2.join(), cf3.join()));
// ✅ Обработка ошибок
CompletableFuture.allOf(cf1, cf2)
.exceptionally(ex -> handleErrors(ex, cf1, cf2));
// ❌ allOf когда нужна последовательность
// ❌ Игнорирование что allOf возвращает Void
Когда НЕ использовать allOf
- Нужны результаты по мере готовности — обрабатывайте каждый CF отдельно
- Один failure не должен отменять остальные — allOf бросает при первой ошибке
// ❌ allOf — одна ошибка роняет всё
CompletableFuture.allOf(cf1, cf2, cf3)
.exceptionally(ex -> null); // все результаты потеряны
// ✅ Обрабатывайте каждый CF отдельно
cf1.handle((r, ex) -> ex != null ? fallback1 : r);
cf2.handle((r, ex) -> ex != null ? fallback2 : r);
cf3.handle((r, ex) -> ex != null ? fallback3 : r);
🎯 Шпаргалка для интервью
Обязательно знать:
- allOf() принимает N CompletableFuture, возвращает CompletableFuture
- Завершается когда ВСЕ CF завершены, или при первой ошибке
- Для результатов нужно вызывать join() на каждом исходном CF
- Latency = max(все CF) — все выполняются параллельно
- Паттерн allAsList: allOf + stream.map(CompletableFuture::join).toList()
Частые уточняющие вопросы:
- allOf возвращает результаты? — Нет, Void. join() на каждом CF отдельно
- Что если один CF упадёт? — allOf завершится с CompletionException
- allOf блокирует? — Нет, возвращает CF. allOf().join() блокирует
- Как получить partial results при ошибке? — Проверять isDone() на каждом CF
Красные флаги (НЕ говорить):
- «allOf возвращает массив результатов» — он возвращает Void
- «allOf продолжает работу при ошибке одного CF» — завершается с ошибкой
- «allOf().join() неблокирующий» — join() блокирует до завершения
Связанные темы:
- [[10. Что делает метод anyOf() и в каких случаях он полезен]]
- [[8. Как комбинировать результаты нескольких CompletableFuture]]
- [[16. Как правильно выполнить несколько параллельных запросов к микросервисам]]
- [[6. Как обрабатывать исключения в цепочке CompletableFuture]]