1. введение
Ядро Java предоставляет базовый API для асинхронных вычислений – Будущее. CompletableFuture |/является одной из его новейших реализаций.
Vavr предоставляет свою новую функциональную альтернативу API Future . В этой статье мы обсудим новый API и покажем, как использовать некоторые из его новых функций.
Дополнительные статьи о Var можно найти здесь .
2. Зависимость Maven
API Future включен в зависимость Var Maven.
Итак, давайте добавим его в ваш pom.xml :
io.vavr vavr 0.9.2
Мы можем найти последнюю версию зависимости от Maven Central .
3. Будущее Вавра
То Будущее может находиться в одном из двух состояний:
- В ожидании – вычисление продолжается
- Завершено – вычисление завершилось успешно с результатом, не удалось с исключением или было отменено
Основное преимущество перед ядром Java Будущее заключается в том, что мы можем легко регистрировать обратные вызовы и составлять операции неблокирующим способом.
4. Основные Будущие Операции
4.1. Запуск Асинхронных Вычислений
Теперь давайте посмотрим, как мы можем начать асинхронные вычисления с помощью Var:
String initialValue = "Welcome to "; FutureresultFuture = Future.of(() -> someComputation());
4.2. Извлечение значений из будущего
Мы можем извлечь значения из Future , просто вызвав один из методов get() или getOrElse() :
String result = resultFuture.getOrElse("Failed to get underlying value.");
Разница между get() и getOrElse() заключается в том, что get() является самым простым решением, в то время как getOrElse() позволяет нам возвращать значение любого типа в случае, если мы не смогли получить значение внутри Будущего .
Рекомендуется использовать getOrElse () , чтобы мы могли обрабатывать любые ошибки, возникающие при попытке получить значение из Future . Для простоты мы просто будем использовать get() в следующих нескольких примерах.
Обратите внимание, что метод get() блокирует текущий поток, если необходимо дождаться результата.
Другой подход заключается в вызове неблокирующего метода GetValue () , который возвращает параметр > , который будет пустым, пока ожидаются вычисления.
Затем мы можем извлечь результат вычисления, который находится внутри объекта Try :
Option> futureOption = resultFuture.getValue(); Try futureTry = futureOption.get(); String result = futureTry.get();
Иногда нам нужно проверить, содержит ли Future значение, прежде чем извлекать из него значения.
Мы можем просто сделать это с помощью:
resultFuture.isEmpty();
Важно отметить, что метод isEmpty() блокирует – он будет блокировать поток до тех пор, пока его работа не будет завершена.
4.3. Изменение службы ExecutorService по умолчанию
Фьючерсы используют ExecutorService для асинхронного выполнения своих вычислений. По умолчанию ExecutorService является Executors.newCachedThreadPool() .
Мы можем использовать другой ExecutorService , передав реализацию по нашему выбору:
@Test public void whenChangeExecutorService_thenCorrect() { String result = Future.of(newSingleThreadExecutor(), () -> HELLO) .getOrElse(error); assertThat(result) .isEqualTo(HELLO); }
5. Выполнение Действий По Завершении
API предоставляет метод onSuccess () , который выполняет действие, как только Future завершается успешно.
Аналогично, метод on Failure() выполняется при сбое Будущего .
Давайте рассмотрим краткий пример:
FutureresultFuture = Future.of(() -> appendData(initialValue)) .onSuccess(v -> System.out.println("Successfully Completed - Result: " + v)) .onFailure(v -> System.out.println("Failed - Result: " + v));
Метод onComplete() принимает действие, которое будет выполнено, как только Future завершит свое выполнение, независимо от того, было ли Future успешным. Метод andThen() похож на onComplete() – он просто гарантирует, что обратные вызовы выполняются в определенном порядке:
FutureresultFuture = Future.of(() -> appendData(initialValue)) .andThen(finalResult -> System.out.println("Completed - 1: " + finalResult)) .andThen(finalResult -> System.out.println("Completed - 2: " + finalResult));
6. Полезные операции с Фьючерсами
6.1. Блокировка текущего потока
Метод await() имеет два случая:
- если Future находится в ожидании, он блокирует текущий поток до тех пор, пока будущее не завершится
- если Future завершен, он завершается немедленно
Использование этого метода очень просто:
resultFuture.await();
6.2. Отмена вычисления
Мы всегда можем отменить вычисление:
resultFuture.cancel();
6.3. Получение базового сервиса ExecutorService
Чтобы получить ExecutorService , который используется Future , мы можем просто вызвать ExecutorService() :
resultFuture.executorService();
6.4. Получение Броска из Неудачного будущего
Мы можем сделать это с помощью метода getCause () , который возвращает Throwable , завернутый в io.var.control.Опция объект.
Позже мы можем извлечь Throwable из объекта Option :
@Test public void whenDivideByZero_thenGetThrowable2() { FutureresultFuture = Future.of(() -> 10 / 0) .await(); assertThat(resultFuture.getCause().get().getMessage()) .isEqualTo("/ by zero"); }
Кроме того, мы можем преобразовать наш экземпляр в Future holding a Выбрасываемый экземпляр с использованием метода failed() :
@Test public void whenDivideByZero_thenGetThrowable1() { FutureresultFuture = Future.of(() -> 10 / 0); assertThatThrownBy(resultFuture::get) .isInstanceOf(ArithmeticException.class); }
6.5. завершено(), является успехом () и является неудачей()
Эти методы в значительной степени объясняют сами себя. Они проверяют, завершен ли Future , успешно ли он завершен или с ошибкой. Все они, конечно, возвращают логические значения.
Мы собираемся использовать эти методы в предыдущем примере:
@Test public void whenDivideByZero_thenCorrect() { FutureresultFuture = Future.of(() -> 10 / 0) .await(); assertThat(resultFuture.isCompleted()).isTrue(); assertThat(resultFuture.isSuccess()).isFalse(); assertThat(resultFuture.isFailure()).isTrue(); }
6.6. Применение вычислений поверх будущего
Метод map() позволяет нам применять вычисления поверх ожидающего Будущего:
@Test public void whenCallMap_thenCorrect() { FuturefutureResult = Future.of(() -> "from Baeldung") .map(a -> "Hello " + a) .await(); assertThat(futureResult.get()) .isEqualTo("Hello from Baeldung"); }
Если мы передадим функцию, которая возвращает Future методу map () , мы можем получить вложенную структуру Feature . Чтобы избежать этого, мы можем использовать метод flatMap() :
@Test public void whenCallFlatMap_thenCorrect() { Future
6.7. Трансформация фьючерсов
Метод transform Value() может использоваться для применения вычисления поверх Будущего и изменения значения внутри него на другое значение того же типа или другого типа:
@Test public void whenTransform_thenCorrect() { Future
6.8. Сжатие фьючерсов
API предоставляет метод zip () , который объединяет Фьючерсы в кортежи – кортеж представляет собой набор из нескольких элементов, которые могут быть связаны или не связаны друг с другом. Они также могут быть разных типов. Давайте рассмотрим краткий пример:
@Test public void whenCallZip_thenCorrect() { Futuref1 = Future.of(() -> "hello1"); Future f2 = Future.of(() -> "hello2"); assertThat(f1.zip(f2).get()) .isEqualTo(Tuple.of("hello1", "hello2")); }
Здесь следует отметить, что результирующее Будущее будет ожидаться до тех пор, пока по крайней мере одна из базовых Функций все еще находится в ожидании.
6.9. Конвертация между Фьючерсами и завершенными фьючерсами
API поддерживает интеграцию с java.util.CompletableFuture . Таким образом, мы можем легко преобразовать Future в CompletableFuture , если мы хотим выполнять операции, которые поддерживает только основной Java API.
Давайте посмотрим, как мы можем это сделать:
@Test public void whenConvertToCompletableFuture_thenCorrect() throws Exception { CompletableFutureconvertedFuture = Future.of(() -> HELLO) .toCompletableFuture(); assertThat(convertedFuture.get()) .isEqualTo(HELLO); }
Мы также можем преобразовать CompletableFuture в Future с помощью метода from CompletableFuture () .
6.10. Обработка исключений
После сбоя Future мы можем справиться с ошибкой несколькими способами.
Например, мы можем использовать метод recover() для возврата другого результата, например сообщения об ошибке:
@Test public void whenFutureFails_thenGetErrorMessage() { Futurefuture = Future.of(() -> "Hello".substring(-1)) .recover(x -> "fallback value"); assertThat(future.get()) .isEqualTo("fallback value"); }
Или мы можем вернуть результат другого функции вычисления с помощью recover With() :
@Test public void whenFutureFails_thenGetAnotherFuture() { Futurefuture = Future.of(() -> "Hello".substring(-1)) .recoverWith(x -> Future.of(() -> "fallback value")); assertThat(future.get()) .isEqualTo("fallback value"); }
Метод fall back To () – это еще один способ обработки ошибок. Он вызывается на Future и принимает другой Future в качестве параметра.
Если первый Future успешен, то он возвращает свой результат. В противном случае, если второй Future будет успешным, он вернет свой результат. Если обе функции /не работают, то метод failed() возвращает Future a Throwable , который содержит ошибку первого Feature :
@Test public void whenBothFuturesFail_thenGetErrorMessage() { Futuref1 = Future.of(() -> "Hello".substring(-1)); Future f2 = Future.of(() -> "Hello".substring(-2)); Future errorMessageFuture = f1.fallbackTo(f2); Future errorMessage = errorMessageFuture.failed(); assertThat( errorMessage.get().getMessage()) .isEqualTo("String index out of range: -1"); }
7. Заключение
В этой статье мы увидели, что такое Будущее , и узнали некоторые из его важных концепций. Мы также рассмотрели некоторые функции API, используя несколько практических примеров.
Полная версия кода доступна на GitHub .