Вопрос 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 failed", 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]]