CompletableFutureの公式ドキュメントを読んでも分からないので Java逆引きレシピを片手に理解する　



#
この記事はチームラボエンジニアリングの9日目の記事です。

本記事ではCompletableFutureについて書きます。

# 背景
CompletableFutureの公式ドキュメントを呼んでも分からないので
Java逆引きレシピを片手に理解する

# 参考URL 参考書
[CompletableFuture 英語版Java11](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html)
[CompletableFuture 日本語版Java8](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CompletableFuture.html)
[Java逆引きレシピ](https://www.amazon.co.jp/Java%E9%80%86%E5%BC%95%E3%81%8D%E3%83%AC%E3%82%B7%E3%83%94-%E7%AC%AC2%E7%89%88-%E7%AB%B9%E6%B7%BB-%E7%9B%B4%E6%A8%B9/dp/4798158445)







## 並行処理の種類、違い
スレッド：スレッド内で行った処理をその後で引き継げない

future：getメソッドの呼び出しがスレッドをブロックしてしまう

completablefuture：呼び出し元がブロックされない


## thenApplyとthenAcceptの違い
thenApply ・・・ メソッド内で処理した結果を次のメソッドに引き渡せる
thenAccept ・・・ メソッド内で処理した結果を次のメソッドに引き渡せない

### thenApplyの場合

```java

    CompletableFuture<String> future1 =
        CompletableFuture.supplyAsync(
            () -> {
              // 時間のかかる処理
              try {
                Thread.sleep(1000);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
              return "java1";
            });


// thenApplyの場合
    future1
        .thenApply(
            res -> {
              return res; // resには、future1のreturnで定義した「java1」が入っている
            })
        .thenApply(
            res -> {
              return res; // resには1回目のthenApplyの戻り値の「java1」が入っている
            })
        .thenAccept(
            res -> {
              System.out.println(res); // resには2回目のthenApplyの戻り値の「java1」が入っており、コンソールに出力される
            });

// コンソール出力
// java1

```

thenApplyの値を次のthenApplyに引き渡せています。


### thenAcceptの場合

```java

    CompletableFuture<String> future1 =
        CompletableFuture.supplyAsync(
            () -> {
              // 時間のかかる処理
              try {
                Thread.sleep(1000);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
              return "java1";
            });

// thenAcceptの場合
    future1.thenAccept(
        res -> {
          System.out.println(res); // return res;を書いて、次のメソッドに値を渡すことはできない
        });

// コンソール出力
// java1

```

thenAccept()メソッド内にreturn を書くことはできない。


理由は、公式ドキュメントを改めて見てみると分かるのですが

```
// thenApply
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)

// thenAccept
public CompletableFuture <Void> thenAccept(Consumer<? super T> action)
```

thenAcceptには、戻り値にVoidと書いてあるから、そりゃ次に引き渡せないよね、なるほど






## thenCombine　とは

**このステージ** と **指定された他のステージ**の両方が正常終了した際に実行される新しいCompletionStageを返します
[クラスCompletableFuture<T> - thenCombineについて](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CompletableFuture.html#thenCombine-java.util.concurrent.CompletionStage-java.util.function.BiFunction-)

↓
上記文章をさらに自分でも分かるように解釈すると、、、
**このステージ = future2**
と
**指定された他のステージ = future3**
の両方が正常終了した際に
**実行される新しいCompletionStage = `(String s1, String s2) -> s1 + " " + s2`**
を返します



```java

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(
            () -> {
              // 時間のかかる処理
              try{
                Thread.sleep(1000);
              } catch (InterruptedException e){
                e.printStackTrace();
              }
              return "java1";
            }
    );

    CompletableFuture<String> future2 = future1.thenApply(
            s -> {
              // 時間のかかる処理
              try{
                Thread.sleep(1000);
              } catch (InterruptedException e){
                e.printStackTrace();
              }
              return "java2";
            }
    );

    CompletableFuture<String> future3 = future1.thenApply(
            s -> {
              // 時間のかかる処理
              try{
                Thread.sleep(1000);
              } catch (InterruptedException e){
                e.printStackTrace();
              }
              return "java3";
            }
    );

    // thenCombineにて、future2と第一引数(future3)の両方が正常終了した際に、
    // (String s1, String s2) -> s1 + " " + s2を実行している
    future2.thenCombine(future3, (String s1, String s2) -> s1 + " " + s2)
            .thenAccept(System.out::println);

// コンソール出力
// java2 java3

```



## thenApplyとthenApplyAsyncの違い

コールバックを設定するメソッドには、メソッド名の末尾に「Async」がついたバージョンもある
「Asyncあり」 前のタスクと同一スレッド上で実行される
「Asyncなし」 新たなタスクとしてスレッドが割り当てられる

thenApply ・・・ future1→future3→future2の順番に実行される
thenApplyAsync ・・・ future4実行後に、future5とfuture6が並行に実行される


### thenApply

このステージが正常に完了したときに、このステージの結果を指定された関数への引数に設定して実行される新しいCompletionStageを返します。
[thenApply](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CompletableFuture.html#thenApply-java.util.function.Function-)

下記のコードだと
future1→future3→future2の順番に実行される


```java

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(
            () -> {
              // 時間のかかる処理
              try{
                Thread.sleep(1000);
              } catch (InterruptedException e){
                e.printStackTrace();
              }
              return "java1";
            }
    );

    CompletableFuture<String> future2 = future1.thenApply(
            s -> {
              // 時間のかかる処理
              try{
                Thread.sleep(1000);
              } catch (InterruptedException e){
                e.printStackTrace();
              }
              return "java2";
            }
    );

    CompletableFuture<String> future3 = future1.thenApply(
            s -> {
              // 時間のかかる処理
              try{
                Thread.sleep(1000);
              } catch (InterruptedException e){
                e.printStackTrace();
              }
              return "java3";
            }
    );

    // future1→future3→future2の順番に実行される
    future2.thenCombine(future3, (String s1, String s2) -> s1 + " " + s2)
            .thenAccept(System.out::println);

// コンソール出力
// java2 java3

```

### thenApplyAsync

このステージが正常に完了したときに、このステージの結果を指定された関数への引数に設定し、このステージのデフォルトの非同期実行機能を使用して実行される新しいCompletionStageを返します。
[thenApplyAsync](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CompletableFuture.html#thenApplyAsync-java.util.function.Function-)

自分なりに解釈すると
**このステージ(future4)**が正常に完了したときに、
このステージの結果を
指定された関数()への
**引数(ラムダ式内のsのこと)**に設定し、
このステージのデフォルトの非同期実行機能を使用して
**実行される(ラムダ式の中身)**
新しいCompletionStageを返します。

下記のコードだと
future4実行後に、future5とfuture6が並列に実行される



```java
      CompletableFuture<String> future4 =
              CompletableFuture.supplyAsync(
                      () -> {
                          // 時間のかかる処理
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          return "java4";
                      });

      CompletableFuture<String> future5 =
              future4.thenApplyAsync(
                      s -> {
                          // 時間のかかる処理
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          return "java5";
                      });

      CompletableFuture<String> future6 =
              future4.thenApplyAsync(
                      s -> {
                          // 時間のかかる処理
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          return "java6";
                      });


      // future4実行後に、future5とfuture6が並列に実行される
      future5
              .thenCombine(future6, (String s1, String s2) -> s1 + " " + s2)
              .thenAccept(System.out::println);

// コンソール出力
// java5 java6

```



## CompletableFutureのエラー処理

非同期処理（supplyAsync）が例外で失敗した場合、thenApplyやthenAcceptメソッドで指定したコールバックは呼び出されない
この場合、whenComplete()メソッドやexceptionally()メソッドを使ってハンドリングを行う

### [whenComplete](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CompletableFuture.html#whenComplete-java.util.function.BiConsumer-)



### [exceptionally](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CompletableFuture.html#exceptionally-java.util.function.Function-)



## thenComposeとthenApplyの違い

これを読むと、通常はthenApplyを使って

[thenCompose](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CompletableFuture.html#thenCompose-java.util.function.Function-)
このステージが正常に完了したときに、**このステージを指定された関数への**引数に設定して実行される新しいCompletionStageを返します。

[thenApply](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CompletableFuture.html#thenApply-java.util.function.Function-)
このステージが正常に完了したときに、**このステージの結果を指定された関数への**引数に設定して実行される新しいCompletionStageを返します。

```
public <U> CompletableFuture<U> thenCompose(Function<? super T,? extends CompletionStage<U>> fn)

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
```

公式ドキュメントを読んでも違いが分からない、、、


[CompletableFutureを勉強中。thenApplyとthenComposeで混乱中。追記：そしてうらがみさんに色々教えてもらいました！(∩´∀｀)∩ﾜｰｲ](https://bufferings.hatenablog.com/entry/2016/06/08/084134)
「thenComposeの使いどころのひとつは、すでにCompletableFutureを返すAPIがあって、それと組み合わせるときかなー、と思いました。」

こちらの記事を参考させていただいて、
すでにCompletableFutureを返すAPIがある場合、thenCompose
新しく作成する場合、thenApply
とのように、基本はthenApplyをつかえば良いのかなとざっくり理解。



# まとめ
thenApplyメソッドの使い方や、実行されるタイミングが分からなかったのでこれを機会に知れて良かった