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 를 사용하여 계산을 비동기식으로 실행합니다. 기본 ExecutorService 는 Executors.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. Futures 와 CompletableFutures 간의 전환
API는 java.util.CompletableFuture 와의 통합을 지원합니다 . 따라서 핵심 Java API만 지원하는 작업을 수행하려는 경우 Future 를 CompletableFuture 로 쉽게 변환할 수 있습니다 .
어떻게 할 수 있는지 봅시다:
@Test
public void whenConvertToCompletableFuture_thenCorrect()
throws Exception {
CompletableFuture<String> convertedFuture = Future.of(() -> HELLO)
.toCompletableFuture();
assertThat(convertedFuture.get())
.isEqualTo(HELLO);
}
fromCompletableFuture() 메서드 를 사용하여 CompletableFuture 를 Future 로 변환할 수도 있습니다 .
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 에서 사용할 수 있습니다 .