Vavrの未来への紹介

Vavrの未来の紹介

1. 前書き

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

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

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

2. メーベン依存

FutureAPIはVavrMaven依存関係に含まれています。

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


    io.vavr
    vavr
    0.9.2

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

3. VavrのFuture

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

  • 保留中-計算が進行中です

  • 完了-計算は結果で正常に終了したか、例外で失敗したか、キャンセルされました

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

4. 基本的なFuture操作

4.1. 非同期計算の開始

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

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

4.2. Futureから値を取得する

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

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

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

Futureから値を取得しようとしたときに発生したエラーを処理できるように、getOrElse()を使用することをお勧めします。 For the sake of simplicity, we’ll just use get() in the next few examples.

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

別のアプローチは、ノンブロッキングgetValue()メソッドを呼び出すことです。このメソッドは、will be empty as long as computation is pending.であるOption<Try<T>>を返します。

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

Option> futureOption = resultFuture.getValue();
Try 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 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 resultFuture = Future.of(() -> appendData(initialValue))
  .andThen(finalResult -> System.out.println("Completed - 1: " + finalResult))
  .andThen(finalResult -> System.out.println("Completed - 2: " + finalResult));

6. Futuresでの便利な操作

6.1. 現在のスレッドをブロックする

メソッドawait()には2つのケースがあります。

  • 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 resultFuture = Future.of(() -> 10 / 0)
      .await();

    assertThat(resultFuture.getCause().get().getMessage())
      .isEqualTo("/ by zero");
}

さらに、failed()メソッドを使用して、Throwableインスタンスを保持するFutureにインスタンスを変換できます。

@Test
public void whenDivideByZero_thenGetThrowable1() {
    Future resultFuture = Future.of(() -> 10 / 0);

    assertThatThrownBy(resultFuture::get)
      .isInstanceOf(ArithmeticException.class);
}

6.5. isCompleted(), isSuccess(),およびisFailure()

これらの方法はほとんど自明です。 Futureが正常に完了したか、失敗したかを確認します。 もちろん、それらはすべてbooleanの値を返します。

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

@Test
public void whenDivideByZero_thenCorrect() {
    Future 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 futureResult = Future.of(() -> "from example")
      .map(a -> "Hello " + a)
      .await();

    assertThat(futureResult.get())
      .isEqualTo("Hello from example");
}

Futureを返す関数をmap()メソッドに渡すと、ネストされたFuture構造になってしまう可能性があります。 これを回避するために、flatMap()メソッドを活用できます。

@Test
public void whenCallFlatMap_thenCorrect() {
    Future futureMap = Future.of(() -> 1)
      .flatMap((i) -> Future.of(() -> "Hello: " + i));

    assertThat(futureMap.get()).isEqualTo("Hello: 1");
}



6.7. Futuresの変換

メソッドtransformValue()を使用して、Futureの上に計算を適用し、その中の値を同じタイプまたは異なるタイプの別の値に変更できます。

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

    assertThat(future.get()).isEqualTo(HELLO + 5);
}



6.8. Futuresの圧縮

APIは、Futuresをタプルにまとめるzip()メソッドを提供します。タプルは、相互に関連している場合と関連していない場合があるいくつかの要素のコレクションです。 また、さまざまなタイプにすることもできます。 簡単な例を見てみましょう:

@Test
public void whenCallZip_thenCorrect() {
    Future f1 = Future.of(() -> "hello1");
    Future 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 convertedFuture = Future.of(() -> HELLO)
      .toCompletableFuture();

    assertThat(convertedFuture.get())
      .isEqualTo(HELLO);
}

fromCompletableFuture()メソッドを使用してCompletableFutureFutureに変換することもできます。

6.10. 例外処理

Futureに障害が発生した場合、いくつかの方法でエラーを処理できます。

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

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

    assertThat(future.get())
      .isEqualTo("fallback value");
}

または、recoverWith()を使用して別のFuture計算の結果を返すことができます。

@Test
public void whenFutureFails_thenGetAnotherFuture() {
    Future 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を返します。これは、最初のFutureのエラーを保持します。

@Test
public void whenBothFuturesFail_thenGetErrorMessage() {
    Future f1 = 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. 結論

この記事では、Futureとは何かを見て、その重要な概念のいくつかを学びました。 また、いくつかの実用的な例を使用して、APIのいくつかの機能についても説明しました。

コードのフルバージョンはover on GitHubで入手できます。