Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
CompletableFuture.supplyAsync() / thenApply() / join()
非同期処理を宣言的に記述する仕組みです(Java 8以降)。複数の非同期タスクを連結・合成したり、エラーハンドリングを一連のチェーンで書いたりできます。
構文
import java.util.concurrent.*;
// 非同期で処理を実行し結果を返します(Supplier)。
CompletableFuture<T> cf = CompletableFuture.supplyAsync(() -> /* T型の値を返す処理 */);
// 非同期で処理を実行しますが結果は返しません(Runnable)。
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> { /* 処理 */ });
// 結果を変換して次の処理に渡します(Functionを受け取る)。
cf.thenApply(result -> /* 変換処理 */);
// 結果を受け取って別のCompletableFutureを返します。
cf.thenCompose(result -> /* CompletableFutureを返す処理 */);
// 結果を消費します(戻り値なし)。
cf.thenAccept(result -> { /* 処理 */ });
// 例外が発生した場合のデフォルト値を返します。
cf.exceptionally(ex -> /* デフォルト値 */);
// 結果またはエラーの両方を処理します。
cf.handle((result, ex) -> /* 処理 */);
// 結果が返るまでブロックして値を取得します。
T value = cf.join();
T value = cf.get(); // InterruptedException, ExecutionException が発生します。
主なメソッド一覧
| メソッド | 概要 |
|---|---|
| supplyAsync(Supplier) | 非同期でサプライヤーを実行し、戻り値を持つ CompletableFuture を返します。 |
| runAsync(Runnable) | 非同期でRunnableを実行します。戻り値は CompletableFuture<Void> です。 |
| thenApply(Function) | 完了した結果を受け取り変換して次のステージに渡します。 |
| thenAccept(Consumer) | 完了した結果を受け取って処理します。次のステージには Void を渡します。 |
| thenCompose(Function) | 結果を受け取り新たな CompletableFuture を返してフラットに連結します。 |
| exceptionally(Function) | 例外発生時にデフォルト値を返します。 |
| handle(BiFunction) | 結果と例外の両方を処理できます。正常・異常どちらの場合も呼び出されます。 |
| allOf(futures...) | すべての CompletableFuture が完了するまで待ちます。 |
| anyOf(futures...) | いずれかの CompletableFuture が完了したら進みます。 |
| join() | 結果を返します。get() とは異なりチェック例外をスローしません。 |
サンプルコード
import java.util.concurrent.*;
// 非同期でデータを取得して変換します。
CompletableFuture<String> cf = CompletableFuture
.supplyAsync(() -> {
// 時間のかかる処理(API呼び出しなど)を模擬します。
try { Thread.sleep(500); } catch (InterruptedException e) {}
return "hello";
})
.thenApply(String::toUpperCase) // 『HELLO』に変換します。
.thenApply(s -> s + " WORLD"); // 『HELLO WORLD』に変換します。
System.out.println(cf.join()); // 『HELLO WORLD』と出力されます。
// exceptionally() でエラー時のデフォルト値を返します。
CompletableFuture<Integer> safeCf = CompletableFuture
.supplyAsync(() -> {
if (true) throw new RuntimeException("処理に失敗しました。");
return 42;
})
.exceptionally(ex -> {
System.out.println("エラー: " + ex.getMessage());
return -1; // デフォルト値を返します。
});
System.out.println(safeCf.join()); // 『-1』と出力されます。
// allOf() で複数の非同期タスクをまとめて待機します。
CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> 30);
CompletableFuture.allOf(task1, task2, task3).join();
int total = task1.join() + task2.join() + task3.join();
System.out.println("合計: " + total); // 『合計: 60』と出力されます。
概要
『CompletableFuture』を使うことで、コールバック地獄に陥らずに非同期処理を直列・並列に組み合わせることができます。thenApply() は結果の変換、thenCompose() は非同期タスクの連結(フラットマップ)、handle() は正常・異常を問わず処理する汎用ハンドラとして使い分けます。
デフォルトでは共通の ForkJoinPool.commonPool() 上で実行されます。カスタムスレッドプールを使いたい場合は supplyAsync(supplier, executor) のように Executor を第2引数に渡してください。
スレッドプールの管理には『ExecutorService / Executors.newFixedThreadPool()』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。