CompletableFuture
CompletableFuture — способ запустить задачу асинхронно и продолжить работу без блокировки, а когда результат будет готов — обработать его. В отличие от Future (который можно только ждать через .get()), CompletableFuture позволяет задать цепочку обработки.
// Запустить задачу в фоне
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return fetchDataFromApi(); // выполнится в другом потоке
});
// Когда результат готов — обработать (тоже асинхронно)
future
.thenApply(data -> parseJson(data)) // преобразовать результат
.thenAccept(user -> saveToDb(user)) // использовать (без возврата)
.exceptionally(ex -> { // обработать ошибку
log.error("Ошибка: " + ex.getMessage());
return null;
});
Основные методы:
// Запуск
CompletableFuture.runAsync(() -> doWork()) // без результата
CompletableFuture.supplyAsync(() -> "результат") // с результатом
// Цепочка
.thenApply(r -> transform(r)) // преобразовать: T → U
.thenAccept(r -> consume(r)) // использовать: T → void
.thenRun(() -> doNext()) // выполнить следующее: () → void
.thenCompose(r -> anotherFuture(r)) // flatMap для CF
// Параллельно
CompletableFuture.allOf(cf1, cf2, cf3) // подождать все
CompletableFuture.anyOf(cf1, cf2, cf3) // подождать первый
Копнуть глубже
thenApply vs thenCompose. Как map vs flatMap в Stream:
thenApply(f)—fвозвращает значение → оборачивается в CFthenCompose(f)—fсама возвращает CF → не оборачивается (нет вложенности)
// thenApply: CF<CF<User>> — плохо, вложенный Future
CompletableFuture<CompletableFuture<User>> nested =
cf.thenApply(id -> findUserAsync(id));
// thenCompose: CF<User> — хорошо, плоский результат
CompletableFuture<User> flat =
cf.thenCompose(id -> findUserAsync(id));
Пул потоков по умолчанию. supplyAsync без второго аргумента использует ForkJoinPool.commonPool() — общий пул для всего приложения. Для I/O-задач (сеть, БД) лучше передавать свой executor, иначе можно заблокировать общий пул долгими задачами:
ExecutorService ioPool = Executors.newFixedThreadPool(20);
CompletableFuture.supplyAsync(() -> callDatabase(), ioPool);
Комбинирование результатов:
CompletableFuture<User> userFuture = fetchUserAsync(id);
CompletableFuture<Account> accountFuture = fetchAccountAsync(id);
// Подождать оба и объединить
CompletableFuture<String> result = userFuture.thenCombine(
accountFuture,
(user, account) -> user.name() + ": " + account.balance()
);
join() vs get(). Оба блокируют до получения результата. Разница: get() бросает checked InterruptedException + ExecutionException, join() — unchecked CompletionException. В цепочке CF обычно используют join().
• разницу thenApply и thenCompose, как объединить два CF через thenCombine (если дошёл до 2-го слоя).