1. 소개

Core Java는 비동기 계산을 위한 기본 API인 Future를 제공합니다. CompletableFuture 는 최신 구현 중 하나입니다.

Vavr은 Future API 에 대한 새로운 기능적 대안을 제공합니다 . 이 기사에서는 새 API에 대해 설명하고 새 기능 중 일부를 사용하는 방법을 보여줍니다.

Vavr에 대한 더 많은 기사는 여기 에서 찾을 수 있습니다 .

2. 메이븐 의존성

Future API 는 Vavr Maven 의존성에 포함되어 있습니다.

이제 pom.xml 에 추가해 보겠습니다 .

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.9.2</version>
</dependency>

Maven Central 에서 의존성의 최신 버전을 찾을 수 있습니다 .

3. Vavr의 미래

Future 는 다음 두 가지 상태 중 하나일 수 있습니다.

  • Pending – 계산이 진행 중입니다.
  • Completed – 계산이 결과와 함께 성공적으로 완료되었거나 예외로 인해 실패했거나 취소되었습니다.

핵심 Java Future 에 비해 주요 이점은 쉽게 콜백을 등록하고 비차단 방식으로 작업을 구성할 수 있다는 것입니다.

4. 기본 향후 운영

4.1. 비동기 계산 시작

이제 Vavr을 사용하여 비동기 계산을 시작하는 방법을 살펴보겠습니다.

String initialValue = "Welcome to ";
Future<String> resultFuture = Future.of(() -> someComputation());

4.2. 미래 에서 값 검색

get() 또는 getOrElse() 메서드 중 하나를 호출 하여 Future 에서 값을 추출할 수 있습니다 .

String result = resultFuture.getOrElse("Failed to get underlying value.");

get()getOrElse() 의 차이점은 get ( ) 이 가장 간단한 솔루션인 반면 getOrElse() 는 Future 내부의 값을 검색할 수 없는 경우 모든 유형의 값을 반환할 수 있다는 것 입니다.

Future 에서 값을 검색하는 동안 발생하는 모든 오류를 처리할 수 있도록 getOrElse() 를 사용하는 것이 좋습니다 . 단순화를 위해 다음 몇 가지 예에서 get() 만 사용하겠습니다.

get() 메서드 는 결과를 기다려야 하는 경우 현재 스레드를 차단합니다.

다른 접근 방식은 nonblocking getValue() 메서드를 호출하는 것입니다. 이 메서드 는 계산이 보류되는 동안 비어 있는 Option<Try<T>>반환합니다 .

그런 다음 Try 개체 내부에 있는 계산 결과를 추출할 수 있습니다 .

Option<Try<String>> futureOption = resultFuture.getValue();
Try<String> futureTry = futureOption.get();
String result = futureTry.get();

때로는 Future에서 값을 검색하기 전에 Future 에 값이 포함되어 있는지 확인해야 합니다.

다음을 사용하여 간단히 수행할 수 있습니다.

resultFuture.isEmpty();

isEmpty() 메서드 가 차단 된다는 점에 유의하는 것이 중요합니다. 이 메서드 는 작업이 완료될 때까지 스레드를 차단합니다.

4.3. 기본 ExecutorService 변경

Futures 는 ExecutorService 를 사용하여 계산을 비동기식으로 실행합니다. 기본 ExecutorServiceExecutors.newCachedThreadPool() 입니다.

선택한 구현을 전달하여 다른 ExecutorService 를 사용할 수 있습니다.

@Test
public void whenChangeExecutorService_thenCorrect() {
    String result = Future.of(newSingleThreadExecutor(), () -> HELLO)
      .getOrElse(error);
    
    assertThat(result)
      .isEqualTo(HELLO);
}

5. 완료 시 조치 수행

API는 Future 가 성공적으로 완료 되는 즉시 작업을 수행하는 onSuccess() 메서드를 제공합니다.

유사하게 onFailure() 메서드는 Future 의 실패 시 실행됩니다 .

간단한 예를 살펴보겠습니다.

Future<String> resultFuture = 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() 와 유사합니다 . 콜백이 특정 순서로 실행되도록 보장할 뿐입니다.

Future<String> resultFuture = 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가 완료될 때까지 현재 스레드를 차단합니다.
  • Future 가 완료 되면 즉시 완료됩니다.

이 방법을 사용하는 것은 간단합니다.

resultFuture.await();

6.2. 계산 취소

우리는 항상 계산을 취소할 수 있습니다:

resultFuture.cancel();

6.3. 기본 ExecutorService 검색

Future 에서 사용 하는 ExecutorService 를 얻으려면 간단히 executorService() 를 호출하면 됩니다 .

resultFuture.executorService();

6.4. 실패한 Future 에서 Throwable 얻기

io.vavr.control.Option 개체 에 래핑된 Throwable 을 반환하는 getCause() 메서드 를 사용하여 이를 수행할 수 있습니다 .

나중에 Option 개체 에서 Throwable추출할 수 있습니다 .

@Test
public void whenDivideByZero_thenGetThrowable2() {
    Future<Integer> resultFuture = Future.of(() -> 10 / 0)
      .await();
    
    assertThat(resultFuture.getCause().get().getMessage())
      .isEqualTo("/ by zero");
}

또한, failed() 메소드 를 사용하여 인스턴스를 Throwable 인스턴스를 보유 하는 Future 로 변환할 수 있습니다 .

@Test
public void whenDivideByZero_thenGetThrowable1() {
    Future<Integer> resultFuture = Future.of(() -> 10 / 0);
    
    assertThatThrownBy(resultFuture::get)
      .isInstanceOf(ArithmeticException.class);
}

6.5. isCompleted(), isSuccess()isFailure()

이러한 방법은 거의 자명합니다. Future 가 성공적으로 완료되었는지 실패로 완료되었는지 확인 합니다. 물론 모두 부울 값을 반환 합니다.

이전 예제에서 이러한 방법을 사용할 것입니다.

@Test
public void whenDivideByZero_thenCorrect() {
    Future<Integer> resultFuture = Future.of(() -> 10 / 0)
      .await();
    
    assertThat(resultFuture.isCompleted()).isTrue();
    assertThat(resultFuture.isSuccess()).isFalse();
    assertThat(resultFuture.isFailure()).isTrue();
}

6.6. 미래 위에 계산 적용

map() 메서드를 사용하면 보류 중인 Future 위에 계산을 적용할 수 있습니다 .

@Test
public void whenCallMap_thenCorrect() {
    Future<String> futureResult = Future.of(() -> "from Baeldung")
      .map(a -> "Hello " + a)
      .await();
    
    assertThat(futureResult.get())
      .isEqualTo("Hello from Baeldung");
}

Future반환하는 함수를 map() 메서드에 전달하면 중첩된 Future 구조 로 끝날 수 있습니다 . 이를 방지하기 위해 flatMap() 메서드를 활용할 수 있습니다.

@Test
public void whenCallFlatMap_thenCorrect() {
    Future<Object> futureMap = Future.of(() -> 1)
      .flatMap((i) -> Future.of(() -> "Hello: " + i));
         
    assertThat(futureMap.get()).isEqualTo("Hello: 1");
}

6.7. 미래의 변화

transformValue() 메서드 는 Future 위에 계산을 적용하고 내부 값을 같은 유형 또는 다른 유형의 다른 값으로 변경하는 데 사용할 수 있습니다.

@Test
public void whenTransform_thenCorrect() {
    Future<Object> future = Future.of(() -> 5)
      .transformValue(result -> Try.of(() -> HELLO + result.get()));
                
    assertThat(future.get()).isEqualTo(HELLO + 5);
}

6.8. 압축 선물

API는 Futures 를 튜플로 함께 압축하는 zip() 메서드를 제공합니다 . 튜플은 서로 관련될 수도 있고 관련되지 않을 수도 있는 여러 요소의 모음입니다. 그들은 또한 다른 유형이 될 수 있습니다. 간단한 예를 살펴보겠습니다.

@Test
public void whenCallZip_thenCorrect() {
    Future<String> f1 = Future.of(() -> "hello1");
    Future<String> f2 = Future.of(() -> "hello2");
    
    assertThat(f1.zip(f2).get())
      .isEqualTo(Tuple.of("hello1", "hello2"));
}

여기서 주목해야 할 점 은 기본 Futures 중 적어도 하나 가 여전히 보류 중인 한 결과 Future 가 보류된다는 것입니다.

6.9. FuturesCompletableFutures 간의 전환

API는 java.util.CompletableFuture 와의 통합을 지원합니다 . 따라서 핵심 Java API만 지원하는 작업을 수행하려는 경우 FutureCompletableFuture 로 쉽게 변환할 수 있습니다 .

어떻게 할 수 있는지 봅시다:

@Test
public void whenConvertToCompletableFuture_thenCorrect()
  throws Exception {
 
    CompletableFuture<String> convertedFuture = Future.of(() -> HELLO)
      .toCompletableFuture();
    
    assertThat(convertedFuture.get())
      .isEqualTo(HELLO);
}

fromCompletableFuture() 메서드 를 사용하여 CompletableFutureFuture 로 변환할 수도 있습니다 .

6.10. 예외 처리

Future 가 실패하면 몇 가지 방법으로 오류를 처리할 수 있습니다.

예를 들어 오류 메시지와 같은 다른 결과를 반환하기 위해 복구() 메서드를 사용할 수 있습니다.

@Test
public void whenFutureFails_thenGetErrorMessage() {
    Future<String> future = Future.of(() -> "Hello".substring(-1))
      .recover(x -> "fallback value");
    
    assertThat(future.get())
      .isEqualTo("fallback value");
}

또는, recoverWith() 를 사용하여 다른 Future 계산 결과를 반환할 수 있습니다 .

@Test
public void whenFutureFails_thenGetAnotherFuture() {
    Future<String> future = Future.of(() -> "Hello".substring(-1))
      .recoverWith(x -> Future.of(() -> "fallback value"));
    
    assertThat(future.get())
      .isEqualTo("fallback value");
}

fallbackTo() 메서드 는 오류를 처리하는 또 다른 방법입니다. Future 에서 호출되고 다른 Future 를 매개변수로 받아들입니다.

첫 번째 Future 가 성공하면 결과를 반환합니다. 그렇지 않고 두 번째 Future 가 성공하면 그 결과를 반환합니다. Future 가 모두 실패하면 failed() 메서드는 첫 번째 Future 의 오류를 포함 하는 Throwable 의 Future 를 반환합니다 .

@Test
public void whenBothFuturesFail_thenGetErrorMessage() {
    Future<String> f1 = Future.of(() -> "Hello".substring(-1));
    Future<String> f2 = Future.of(() -> "Hello".substring(-2));
    
    Future<String> errorMessageFuture = f1.fallbackTo(f2);
    Future<Throwable> errorMessage = errorMessageFuture.failed();
    
    assertThat(
      errorMessage.get().getMessage())
      .isEqualTo("String index out of range: -1");
}

7. 결론

이 기사에서 우리는 Future 가 무엇인지 살펴보고 그 중요한 개념 중 일부를 배웠습니다. 또한 몇 가지 실용적인 예를 사용하여 API의 일부 기능을 살펴보았습니다.

코드의 전체 버전은 GitHub 에서 사용할 수 있습니다 .

Generic footer banner