Що робить метод 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 fails", 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]]