Что делает метод 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: 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. Как правильно выполнить несколько параллельных запросов к микросервисам]]