Java | Java 詳細・モダン文法:並行・非同期 – allOf / anyOf

Java Java
スポンサーリンク

allOf / anyOf を一言でいうと

CompletableFuture.allOfCompletableFuture.anyOf は、
複数の非同期処理をまとめて扱うための“ハブ”」です。

allOf は「全部終わったら教えて」。
anyOf は「どれか一つ終わったら教えて」。

この二つを使いこなせると、「たくさんの非同期処理」をきれいに整理して書けるようになります。


allOf:全部終わるのを待つ

基本イメージ

allOf は、
「複数の CompletableFuture が全部終わるのを待つ」ためのメソッドです。

シグネチャはざっくりこうです。

static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
Java

戻り値は CompletableFuture<Void> です。
つまり、「完了した」という事実だけを教えてくれて、中身の値は自分で取りに行きます。

具体例:3 つの API を並列に呼んで、全部揃ってから処理する

例えば、ユーザー画面を描画するために、
ユーザー情報、ポイント情報、通知一覧をそれぞれ別の API から取るとします。
これを順番にやると遅いので、全部並列に投げて、全部揃ったらまとめて使いたい。

CompletableFuture<User> userFuture =
        CompletableFuture.supplyAsync(() -> fetchUser());

CompletableFuture<Point> pointFuture =
        CompletableFuture.supplyAsync(() -> fetchPoint());

CompletableFuture<List<Notification>> notificationFuture =
        CompletableFuture.supplyAsync(() -> fetchNotifications());

CompletableFuture<Void> all =
        CompletableFuture.allOf(userFuture, pointFuture, notificationFuture);

all.join(); // ここで「全部終わる」のを待つ

User user = userFuture.join();
Point point = pointFuture.join();
List<Notification> notifications = notificationFuture.join();

// ここで user / point / notifications を使って画面を組み立てる
Java

ここで大事なのは、
「allOf の戻り値には中身がないので、個々の Future から結果を取り出す」
というパターンに慣れることです。

allOf を使うときに意識したいこと

allOf は「全部終わるまで次に進まない」という性質を持ちます。
つまり、「どれか一つでも遅いものがあると、全体がそれに引きずられる」ということです。

だからこそ、
「この画面は、3 つ全部揃わないと意味がないよね」
という場面で使うのが自然です。

逆に、「どれか欠けていても、とりあえず表示したい」ような場面では、
allOf 一発で待つのではなく、個々の Future をそれぞれ扱う設計も検討した方がよくなります。


anyOf:どれか一つ終わればいい

基本イメージ

anyOf は、
「複数の CompletableFuture のうち、最初に終わったものだけを採用する」ためのメソッドです。

シグネチャはざっくりこうです。

static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
Java

戻り値は CompletableFuture<Object>
「どれが先に終わるか分からない」ので、型は一旦 Object になります。

具体例:速い方の結果だけ使う

例えば、同じデータを取るのに 2 つの経路があるとします。
メインの API と、キャッシュサーバー。
どちらか早い方の結果だけ使いたい。

CompletableFuture<String> fromApi =
        CompletableFuture.supplyAsync(() -> fetchFromApi());

CompletableFuture<String> fromCache =
        CompletableFuture.supplyAsync(() -> fetchFromCache());

CompletableFuture<Object> any =
        CompletableFuture.anyOf(fromApi, fromCache);

Object result = any.join();
String value = (String) result;
Java

ここでは、
先に終わった方の結果が result に入ります。
API が早ければ API の結果、キャッシュが早ければキャッシュの結果。

「どの経路かは気にしない、とにかく一番早い値が欲しい」
というときに、anyOf はとても素直に意図を表現してくれます。

anyOf を使うときに意識したいこと

anyOf は「最初に終わったものだけを採用する」ので、
残りの Future はそのまま走り続けます。

場合によっては、
「勝負がついたら、残りはキャンセルしたい」
という設計もありえます。

その場合は、anyOf の完了後に、
他の Future に対して cancel(true) を呼ぶなど、
一歩踏み込んだ制御が必要になります。

また、戻り値の型が Object なので、
実際には「全部同じ型の Future を渡す」前提で使うことが多いです。
そうすれば、キャストも素直に書けます。


allOf / anyOf とエラーの関係

allOf のエラー

allOf に渡した Future のうち、
どれか一つでも例外で終わると、
allOf 自体も例外として完了します。

つまり、

「3 つのうち 1 つが失敗したら、
allOf.join() で例外が飛ぶ」

という挙動になります。

「全部成功しないと意味がない」処理なら、それで OK です。
「失敗したものだけデフォルト値にしたい」ような場合は、
個々の Future 側で exceptionallyhandle を使って、
「失敗しても正常値に変換しておく」設計にするのが定石です。

anyOf のエラー

anyOf は「最初に終わったもの」を採用するので、
最初に終わった Future が「成功」なら成功、
「例外」なら例外として完了します。

例えば、
3 つの Future のうち 1 つがすぐに失敗し、
残り 2 つは時間がかかる、という状況では、
anyOf は「失敗した Future の結果」を採用してしまいます。

「最初に成功したものが欲しい」
「失敗は無視して、成功したものだけを採用したい」

という場合は、
失敗を「成功扱いのラッパー値」に変換しておくなど、
少し工夫が必要になります。


allOf / anyOf をどう使い分けるか

自分にこう問いかける

allOf と anyOf を選ぶとき、
自分にこう聞いてみてください。

この場面では、“全部必要”か? それとも“どれか一つでいい”か?

全部必要なら allOf。
どれか一つでいいなら anyOf。

さらに、

全部必要だが、一部失敗してもいいのか?
どれか一つでいいが、失敗をどう扱うのか?

といった設計も合わせて考えると、
エラー処理まで含めてきれいなフローが描けます。


まとめ:allOf / anyOf を自分の言葉で説明するなら

あなたの言葉で整理すると、こうなります。

CompletableFuture.allOf は、複数の非同期処理が“全部終わる”のを待つためのハブ。
戻り値は CompletableFuture<Void> なので、個々の結果は元の Future から取り出す。
『この 3 つが揃って初めて意味がある』という場面で使う。

CompletableFuture.anyOf は、複数の非同期処理のうち“最初に終わったもの”だけを採用するハブ。
戻り値は CompletableFuture<Object> で、先に終わった Future の結果が入る。
『どれでもいいから一番早い結果が欲しい』という場面で使う。

どちらを使うかは、
『この処理は全部必要か? それともどれか一つでいいか?』
という問いに対する答えで決まる。」

タイトルとURLをコピーしました