В чем разница между Future и CompletableFuture и как с ними работать
Что проверяют
Понимаете ли вы, чем плох блокирующий get(), и умеете ли строить неблокирующие цепочки.
Как отвечать
Future появился ещё в Java 5 и умеет немного: узнать, готов ли результат, отменить задачу и дождаться результата вызовом get(), который блокирует поток. Ни объединить два будущих результата, ни навесить обработчик завершения, ни завершить его вручную нельзя.
CompletableFuture из Java 8 реализует Future и добавляет то, чего не хватало:
- запуск:
supplyAsync(() -> ..., executor)иrunAsync. Свой пул задавать почти всегда стоит, иначе всё уедет в общий пулForkJoinPool.commonPool; - цепочки:
thenApplyпреобразует результат,thenComposeразворачивает вложенное будущее значение (аналог flatMap),thenAcceptзавершает цепочку побочным действием; - объединение:
thenCombineдля двух результатов,allOfиanyOfдля набора; - ошибки:
exceptionally,handleиwhenCompleteвместо перехвата исключения вокругget(); - ручное завершение:
complete(value), что удобно для мостов к колбэчным API и в тестах; - тайм-ауты:
orTimeoutиcompleteOnTimeoutиз Java 9.
CompletableFuture.supplyAsync(() -> loadUser(id), executor)
.thenCompose(user -> loadOrders(user.id()))
.orTimeout(2, SECONDS)
.exceptionally(e -> List.of());Полезно упомянуть, что в свежих версиях Java для многих задач проще виртуальные потоки: блокирующий код на них дёшев, и сложная цепочка часто не нужна.
Где спотыкаются
Вызывают get() внутри цепочки и теряют весь смысл асинхронности.
Путают thenApply и thenCompose, получая CompletableFuture<CompletableFuture<T>>.
Считают, что cancel(true) прерывает выполняющуюся задачу. Он лишь помечает будущее значение отменённым.