1. 序章

Core Javaは、非同期計算用の基本APIを提供します–Future。CompletableFutureは最新の実装の1つです。

Vavrは、 FutureAPIに代わる新しい機能を提供します。 この記事では、新しいAPIについて説明し、その新機能のいくつかを利用する方法を示します。

Vavrに関するその他の記事は、ここにあります。

2. Mavenの依存関係

Future APIは、VavrMavenの依存関係に含まれています。

それでは、それをpom.xmlに追加しましょう。

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

MavenCentralへの依存関係の最新バージョンを見つけることができます。

3. Vavrの将来

Future は、次の2つの状態のいずれかになります。

  • 保留中–計算は進行中です
  • 完了– 計算は結果で正常に終了したか、例外で失敗したか、キャンセルされました

コアJavaFuture に対する主な利点は、コールバックを簡単に登録し、非ブロッキング方式で操作を構成できることです。

4. 基本的な将来の操作

4.1. 非同期計算の開始

それでは、Vavrを使用して非同期計算を開始する方法を見てみましょう。

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

4.2. Futureからの値の取得

get()または getOrElse()メソッドのいずれかを呼び出すだけで、Futureから値を抽出できます。

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

get() getOrElse()の違いは、 get()が最も単純なソリューションであるのに対し、 getOrElse()では Future 内の値を取得できなかった場合に備えて、任意のタイプの値を返します。

getOrElse()を使用することをお勧めします。これにより、Futureから値を取得しようとしているときに発生するエラーを処理できます。 簡単にするために、次のいくつかの例ではget()を使用します。

get()メソッドは、結果を待つ必要がある場合、現在のスレッドをブロックすることに注意してください。

別のアプローチは、ノンブロッキングを呼び出すことです getValue() メソッド。 オプション >> どれの計算が保留されている限り、空になります。

次に、Tryオブジェクト内にある計算結果を抽出できます。

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

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()には、次の2つのケースがあります。

  • Future が保留中の場合、Futureが完了するまで現在のスレッドをブロックします
  • Future が完了すると、すぐに終了します

この方法の使用は簡単です。

resultFuture.await();

6.2. 計算のキャンセル

いつでも計算をキャンセルできます。

resultFuture.cancel();

6.3. 基になるExecutorServiceの取得

Futureで使用されるExecutorServiceを取得するには、 executeService()を呼び出すだけです。

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 が正常に完了したか、失敗したかを確認します。 もちろん、それらはすべてboolean値を返します。

前の例では、これらのメソッドを使用します。

@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は、 zip()メソッドを提供します。このメソッドは Futures を一緒にタプルに圧縮します。タプルは、相互に関連している場合と関連していない場合があるいくつかの要素のコレクションです。 それらはまた異なるタイプである場合もあります。 簡単な例を見てみましょう。

@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 の少なくとも1つがまだ保留中である限り、結果の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 に障害が発生した場合、いくつかの方法でエラーを処理できます。

たとえば、メソッド restore()を使用して、エラーメッセージなどの別の結果を返すことができます。

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

または、 restoreWith()を使用して、別の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()は、エラーを処理するもう1つの方法です。 Future で呼び出され、別のFutureをパラメーターとして受け入れます。

最初のFutureが成功すると、その結果が返されます。 それ以外の場合、2番目の Future が成功すると、その結果が返されます。 両方のFuturesが失敗した場合、 failed()メソッドはThrowableFutureを返します。これは、最初の[ X151X]将来:

@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から入手できます。