Що робить метод anyOf() і в яких випадках він корисний
Structured Java interview answer with junior, middle, and senior-level explanation.
🟢 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);
});
Типові помилки
- Невідомо який CF завершився:
CompletableFuture.anyOf(cf1, cf2).thenAccept(result -> { // result — Object, незрозуміло від якого CF // Якщо типи різні — потрібно перевіряти if (result instanceof String s) { } if (result instanceof Integer i) { } }); - Решта 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
- Не скасовує решту 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. Як правильно виконати декілька паралельних запитів до мікросервісів]]