Питання 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: найшвидший з декількох серверів, fallback, timeout

Часті уточнюючі питання:

  • anyOf скасовує решту CF? — Ні, потрібно вручну cancel(false)
  • Як дізнатися який CF переміг? — Порівняти result з відомими джерелами
  • anyOf vs applyToEither? — applyToEither для двох CF (типизований), anyOf для N (Object)
  • Що якщо перший CF впав? — anyOf завершиться з помилкою. Потрібна обробка

Червоні прапорці (НЕ говорити):

  • «anyOf повертає типизований результат» — повертає Object, потрібен cast
  • «anyOf автоматично скасовує решту» — ні, continue виконуватися
  • «anyOf гарантує найкращий результат» — тільки найшвидший, не найкращий

Пов’язані теми:

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