mike-neckのブログ

Java or Groovy or Swift or Golang

CompletableFuture の thenApplyAsync メソッドは何度も呼び出せるのか試してみた

最近、自分専用のライブラリーを書いていて、 CompletableFuture#thenApply および CompletableFuture#thenApplyAsync などを複数回呼び出せるのかがわかっていなかったし、 Javadoc にも書いていないようなので実験してみることにしました。


書いたのは次のようなコードです。

@Test
void handlers2() throws InterruptedException {
  final CountDownLatch latch = new CountDownLatch(1);
  final CompletableFuture<String> future =
      CompletableFuture.supplyAsync(
          () -> {
            sleep();
            // ランダムで結果を返す
            // 成功の場合は foo を返す
            // 失敗の場合はRuntimeExceptionが発生
            return Result.random().apply("foo");
          },
          executor);

  // CompletableFuture#thenApplyAsync を2回呼び出す
  final CompletableFuture<String> f1 =
      future.thenApplyAsync(str -> String.format("result -> %s", str));
  final CompletableFuture<String> f2 =
      future.thenApplyAsync(
          str -> {
            throw new RuntimeException(str);
          });

  // 結果を文字列で表示するためのハンドラー
  final CompletableFuture<String> hf1 =
      f1.whenCompleteAsync(
          (str, th) ->
              handle(str, th)
                  .onSuccess(s -> System.out.println(String.format("handler 1 -> %s", s)))
                  .onError(
                      e ->
                          System.out.println(
                              String.format(
                                  "handler 1 error -> %s", e.getClass().getSimpleName()))),
          executor);
  final CompletableFuture<String> hf2 =
      f2.whenCompleteAsync(
          (str, th) ->
              handle(str, th)
                  .onSuccess(s -> System.out.println(String.format("handler 2 -> %s", s)))
                  .onError(
                      e ->
                          System.out.println(
                              String.format(
                                  "handler 2 error -> %s", e.getClass().getSimpleName()))),
          executor);
  CompletableFuture.allOf(hf1, hf2).whenComplete((s, t) -> latch.countDown());
  latch.await();
}

結果は次のようになりました

handler 1 -> result -> foo
handler 2 error -> CompletionException

というわけで、同じ CompletableFutureインスタンスから複数の CompletableFuture を派生させることが可能とわかった


すると次に気になるのは、 CompletableFuture の結果が返ってきてから、 thenApplyAsync を呼び出せるのかという問題

先ほどのメソッドを次のように書き足してみた

@Test
void handlers2() throws InterruptedException {
  // CountDownLatch を追加
  final CountDownLatch finalLatch = new CountDownLatch(1);

  // ... 先ほどのメソッド

  latch.await(); // 先ほどのメソッドの最後の行

  final CompletableFuture<String> f3 =
      future.thenApplyAsync(str -> String.format("after completed: %s", str));
  final CompletableFuture<String> hf3 =
      f3.whenCompleteAsync(
          (str, th) ->
              handle(str, th)
                  .onSuccess(s -> System.out.println(String.format("handler 3 -> %s", s)))
                  .onError(
                      e ->
                          System.out.println(
                              String.format(
                                  "handler 3 error -> %s", e.getClass().getSimpleName()))),
          executor);

  hf3.whenCompleteAsync((s,t) -> finalLatch.countDown());
  finalLatch.await();
}

実行結果はこちら

handler 2 error -> CompletionException
handler 1 -> result -> foo - foo
handler 3 -> after completed: foo - foo

で、 complete した状態の CompletableFuture に対して、関数を適用することも可能だということがわかった。