Як створити CompletableFuture, який вже завершений з результатом?
Іноді потрібно повернути вже готовий CompletableFuture — наприклад, коли дані є в кеші або для тестування.
🟢 Junior Level
Іноді потрібно повернути вже готовий CompletableFuture — наприклад, коли дані є в кеші або для тестування.
completedFuture(value)
// Миттєво завершений CF з результатом
CompletableFuture<String> cf = CompletableFuture.completedFuture("Hello");
cf.thenAccept(s -> System.out.println(s)); // Hello — без затримки
failedFuture(Throwable) — Java 9+
// Миттєво завершений CF з помилкою
CompletableFuture<String> cf = CompletableFuture.failedFuture(
new RuntimeException("Error")
);
cf.exceptionally(ex -> "fallback"); // спіймає помилку
До Java 9 доводилося робити вручну:
CompletableFuture<String> cf = new CompletableFuture<>();
cf.completeExceptionally(new RuntimeException("Error"));
🟡 Middle Level
Навіщо це потрібно в реальних проєктах?
1. Умовна асинхронність (Caching):
public CompletableFuture<Data> getData(String id) {
Data cached = cache.get(id);
if (cached != null) {
return CompletableFuture.completedFuture(cached); // Миттєва відповідь
}
return CompletableFuture.supplyAsync(() -> db.fetch(id)); // Асинхронний запит
}
2. Fallback / Null Object:
return CompletableFuture.completedFuture(Collections.emptyList());
3. Мокування і тестування:
// В Unit-тестах — синхронний режим
when(service.getDataAsync("1")).thenReturn(
CompletableFuture.completedFuture(testData)
);
Критичний нюанс: Потік виконання
Якщо ви викликаєте .thenApply() на вже завершеному CF:
- Колбек виконається в ПОТОЧНОМУ потоці (в тому, який викликає
thenApply) - Якщо CF ще не завершений, колбек виконається в потоці, який завершить CF
Наслідок: Якщо ви очікували асинхронності, але отримали завершений CF, ваш “важкий” колбек може заблокувати головний потік.
Рішення: Використовуйте *Async версії методів, якщо не впевнені в джерелі CF.
// Безпечно: thenApplyAsync завжди переключить потік
completedFuture.thenApplyAsync(data -> heavyTransform(data), executor);
// Небезпечно: може виконатися в поточному потоці
completedFuture.thenApply(data -> heavyTransform(data));
completedStage(value) — Java 12+
// Повертає CompletionStage — не можна завершити вручну
CompletionStage<String> stage = CompletableFuture.completedStage("Hello");
// stage.complete(...) — не скомпілюється!
Імутабельне представлення — безпечніше для публічних API.
🔴 Senior Level
Продуктивність і Highload
- Allocation:
completedFutureстворює об’єкт у купі. В екстремальних випадках (мільйони запитів за сек) це може навантажувати GC. - Project Valhalla: В майбутньому CF може стати Value-типом (або бути оптимізований через Scalar Replacement), що прибере оверхед на алокацію. // Project Valhalla — у розробці, timeline не визначений. // Не розраховуйте на це в production-рішеннях.
Edge Cases
1. Failed Future vs exceptionally:
failedFuture одразу переводить ланцюжок у стан помилки. Усі наступні thenApply будуть пропущені.
2. Short-circuit:
Якщо посередині довгого ланцюжка стоїть exceptionally, який повернув результат, усі наступні thenApply працюватимуть з цим результатом, наче помилки і не було.
Best Practices
// ✅ Використовуйте completedFuture для кешу
if (cached != null) return CompletableFuture.completedFuture(cached);
// ✅ failedFuture для помилок (Java 9+)
return CompletableFuture.failedFuture(new BusinessException("Not found"));
// ✅ completedStage для публічних API (Java 12+)
public CompletionStage<Data> getData() { ... }
// ❌ Не блокуйте в колбеках завершених CF
// ❌ Не ігноруйте що CF вже завершений
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- completedFuture(value) — миттєво завершений CF з результатом
- failedFuture(Throwable) — Java 9+, миттєво завершений CF з помилкою
- completedStage(value) — Java 12+, імутабельне CompletionStage
- completedFuture викликається в ТОМУ Ж потоці — колбеки виконуються синхронно
- completedFuture корисний для кешу, моків, fallback, conditional async
Часті уточнюючі питання:
- Навіщо completedFuture якщо результат вже є? — Для єдиного async API: кеш повертає completedFuture, БД — supplyAsync
- completedFuture.thenApply — в якому потоці виконається? — В поточному (викликаючому) потоці, т.к. CF вже завершений
- Як створити failed CF в Java 8? — new CompletableFuture() + completeExceptionally(ex)
- Коли використовувати completedStage замість completedFuture? — Для публічних API, щоб caller не міг завершити вручну
Червоні прапорці (НЕ говорити):
- «completedFuture створює новий потік» — він синхронний, без потоків
- «completedFuture.thenApplyAsync завжди безпечний» — Async переключить потік, але overhead для легких операцій надмірний
- «failedFuture і exceptionally це одне й те саме» — failedFuture створює CF у стані помилки, exceptionally обробляє помилку існуючого
Пов’язані теми:
- [[26. Чи можна вручну завершити CompletableFuture результатом]]
- [[6. Як обробляти виключення в ланцюжку CompletableFuture]]
- [[16. Що робить метод supplyAsync() і коли його використовувати]]
- [[23. Як тестувати код з CompletableFuture]]