Вопрос 10 · Раздел 19

Что делает метод anyOf() и в каких случаях он полезен

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

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

anyOf() — ждёт завершения ПЕРВОГО из переданных CompletableFuture и возвращает его результат.

CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(100);
    return "Fast";
});

CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(1000);
    return "Slow";
});

// Возвращает результат ПЕРВОГО завершённого
CompletableFuture<Object> any = CompletableFuture.anyOf(fast, slow);

any.thenAccept(result -> System.out.println(result));  // "Fast"

Когда использовать:

  • Запрос к нескольким источникам — кто быстрее ответит
  • Fallback: основной и резервный сервис
  • Таймаут через альтернативный CF

🟡 Middle Level

Как это работает

// Запрос к нескольким серверам
CompletableFuture<String> server1 = queryServerAsync("server1");
CompletableFuture<String> server2 = queryServerAsync("server2");
CompletableFuture<String> server3 = queryServerAsync("server3");

// Берём первый ответ
CompletableFuture<Object> fastest = CompletableFuture.anyOf(server1, server2, server3);

fastest.thenAccept(response -> System.out.println("Got response: " + response));

Важно: anyOf() возвращает CompletableFuture<Object> — нужно привести тип:

CompletableFuture.anyOf(cf1, cf2)
    .thenAccept(result -> {
        String str = (String) result;  // cast нужен!
        System.out.println(str);
    });

Типичные ошибки

  1. Неизвестен какой CF завершился:
    CompletableFuture.anyOf(cf1, cf2).thenAccept(result -> {
     // result — Object, неясно от какого CF
     // Если типы разные — нужно проверять
     if (result instanceof String s) { }
     if (result instanceof Integer i) { }
    });
    
  2. Остальные CF продолжаю выполняться:
    // anyOf не отменяет остальные CF!
    CompletableFuture.anyOf(fast, slow);
    // slow продолжает выполняться в фоне
    

🔴 Senior Level

Internal Implementation

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
    return new OrRelay<>(cfs, null);
}

// Когда первый CF завершается — результат копируется в OrRelay
// Остальные CF игнорируются (но продолжают выполняться)

Архитектурные Trade-offs

anyOf() allOf()
Первый завершённый Все завершены
Object результат Void результат
Fast fail Полное ожидание

Edge Cases

1. Timeout pattern:

CompletableFuture<String> operation = fetchDataAsync();
CompletableFuture<Object> timeout = CompletableFuture
    .delayedExecutor(5, TimeUnit.SECONDS)
    .thenApply(v -> { throw new TimeoutException(); });

CompletableFuture<Object> result = CompletableFuture.anyOf(operation, timeout);

2. Multiple sources with fallback:

CompletableFuture<Data> primary = fetchFromPrimaryAsync();
CompletableFuture<Data> fallback = fetchFromCacheAsync();

CompletableFuture<Object> any = CompletableFuture.anyOf(primary, fallback);

any.thenAccept(result -> {
    Data data = (Data) result;
    // anyOf мог завершиться от fallback, пока primary ещё работает.
    // isCompletedExceptionally() не говорит, КТО победил в гонке.
    // Надёжный способ — сравнивать result с известными источниками.
    if (result == fallbackResult) {
        log.warn("Primary failed or hasn't completed yet, using fallback");
    }
});

3. Cancellation of remaining:

CompletableFuture<String> cf1 = fetchAsync(1);
CompletableFuture<String> cf2 = fetchAsync(2);
CompletableFuture<String> cf3 = fetchAsync(3);

CompletableFuture<Object> any = CompletableFuture.anyOf(cf1, cf2, cf3);

any.thenAccept(result -> {
    // Отменить остальные
    // cancel на уже завершённом CF — no-op. Но код вводит в заблуждение,
    // будто все три ещё работают. Лучше отслеживать победителя.
    cf1.cancel(false);
    cf2.cancel(false);
    cf3.cancel(false);
});

Производительность

anyOf():
- Создание: ~10 ns
- Overhead: ~5 ns на каждый CF
- Первый завершённый — результат

Отмена остальных CF:
- cancel(false): ~100 ns
- Но выполнение может продолжиться

Production Experience

Resilient service call:

@Service
public class ResilientService {
    public CompletableFuture<String> getData(String key) {
        CompletableFuture<String> primary = primaryClient.getAsync(key);
        CompletableFuture<String> secondary = secondaryClient.getAsync(key);
        CompletableFuture<String> cache = cacheClient.getAsync(key);
        
        return CompletableFuture.anyOf(primary, secondary, cache)
            .thenApply(result -> (String) result);
    }
}

When NOT to use anyOf

  • Нужны ВСЕ результаты — используйте allOf
  • Быстрейший может быть от ненадёжного источника — anyOf вернёт первый, не лучший

Best Practices

// ✅ anyOf для fastest response
CompletableFuture.anyOf(cf1, cf2, cf3)
    .thenAccept(result -> process((Type) result));

// ✅ Timeout через anyOf
CompletableFuture.anyOf(operation, timeoutCF);

// ✅ Отмена остальных при завершении
any.thenAccept(r -> { cf1.cancel(false); cf2.cancel(false); });

// ❌ anyOf когда нужны все результаты
// ❌ Игнорирование Object return type
// ❌ Неотменённые остальные CF

🎯 Шпаргалка для интервью

Обязательно знать:

  • anyOf() возвращает результат ПЕРВОГО завершённого CF
  • Возвращает CompletableFuture — нужен cast
  • Не отменяет остальные CF — они продолжают выполняться
  • Latency = min(все CF) — fastest response
  • Use case: fastest из нескольких серверов, fallback, timeout

Частые уточняющие вопросы:

  • anyOf отменяет остальные CF? — Нет, нужно вручную cancel(false)
  • Как узнать какой CF победил? — Сравнить result с известными источниками
  • anyOf vs applyToEither? — applyToEither для двух CF (типизированный), anyOf для N (Object)
  • Что если первый CF упал? — anyOf завершится с ошибкой. Нужна обработка

Красные флаги (НЕ говорить):

  • «anyOf возвращает типизированный результат» — возвращает Object, нужен cast
  • «anyOf автоматически отменяет остальные» — нет, continue выполняться
  • «anyOf гарантирует лучший результат» — только fastest, не best

Связанные темы:

  • [[9. Что делает метод allOf() и когда его использовать]]
  • [[8. Как комбинировать результаты нескольких CompletableFuture]]
  • [[20. Как реализовать timeout для CompletableFuture]]
  • [[16. Как правильно выполнить несколько параллельных запросов к микросервисам]]