Питання 9 · Розділ 19

Що робить метод allOf() і коли його використовувати

Structured Java interview answer with junior, middle, and senior-level explanation.

Мовні версії: English Russian Ukrainian

🟢 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;
    });

Типові помилки

  1. Спроба отримати результати з 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]]