2つのCompletionStagesがある場合、それらをthenCombine
メソッドと組み合わせることができます。
CompletionStage<A> aCompletionStage = getA();
CompletionStage<B> bCompletionStage = getB();
CompletionStage<Combined> combinedCompletionStage =
aCompletionStage.thenCombine(bCompletionStage, (aData, bData) -> combine(aData, bData));
3つ以上のCompletionStageがある場合、thenCombine
メソッドのチェーンを作成できますが、結果を渡すために一時オブジェクトを使用する必要があります。たとえば、以下はorg.Apache.commons.lang3.Tuple
パッケージのPair
とTriple
を使用したソリューションです。
CompletionStage<A> aCompletionStage = getA();
CompletionStage<B> bCompletionStage = getB();
CompletionStage<C> cCompletionStage = getC();
CompletionStage<D> dCompletionStage = getD();
CompletionStage<Combined> combinedDataCompletionStage =
aCompletionStage.thenCombine(bCompletionStage, (Pair::of))
.thenCombine(cCompletionStage, (ab, c) ->
Triple.of(ab.getLeft(), ab.getRight(), c))
.thenCombine(dCompletionStage, (abc, d) ->
combine(abc.getLeft(), abc.getMiddle(), abc.getRight(), d));
複数のCompletionStageの結果を組み合わせるより良い方法はありますか?
ステージ数の増加に合わせて適切にスケーリングする複数のステージを組み合わせる唯一の方法は、CompletableFuture
を使用することです。 CompletionStage
sがCompletableFuture
sではない場合でも、.toCompletableFuture()
を使用して変換できます。
CompletableFuture<A> aCompletionStage = getA().toCompletableFuture();
CompletableFuture<B> bCompletionStage = getB().toCompletableFuture();
CompletableFuture<C> cCompletionStage = getC().toCompletableFuture();
CompletableFuture<D> dCompletionStage = getD().toCompletableFuture();
CompletionStage<Combined> combinedDataCompletionStage = CompletableFuture.allOf(
aCompletionStage, bCompletionStage, cCompletionStage, dCompletionStage)
.thenApply(ignoredVoid -> combine(
aCompletionStage.join(), bCompletionStage.join(),
cCompletionStage.join(), dCompletionStage.join()) );
これには、thenCombine
を介して2つのステージを組み合わせるよりも多くのボイラープレートが含まれていますが、ボイラープレートにステージを追加しても、ボイラープレートは成長しません。
元のthenCombine
のアプローチでも、Triple
は必要ありません。Pair
で十分です。
CompletionStage<Combined> combinedDataCompletionStage =
aCompletionStage.thenCombine(bCompletionStage, (Pair::of)).thenCombine(
cCompletionStage.thenCombine(dCompletionStage, Pair::of),
(ab, cd) -> combine(ab.getLeft(), ab.getRight(), cd.getLeft(), cd.getRight()));
それでも、より多くのステージを組み合わせる場合は、適切にスケーリングされません。
(複雑さに関する)中間のソリューションは次のようになります。
CompletionStage<Combined> combinedDataCompletionStage = aCompletionStage.thenCompose(
a -> bCompletionStage.thenCompose(b -> cCompletionStage.thenCompose(
c -> dCompletionStage.thenApply(d -> combine(a, b, c, d)))));
これは構造が単純ですが、ステージを増やすと拡張性が低下します。
ホルガーの3番目の答え は少し短くできます:
CompletionStage<Combined> combinedDataCompletionStage = aCompletionStage.thenCompose(
a -> bCompletionStage.thenCompose(
b -> cCompletionStage.thenCombine(dCompletionStage,
(c, d) -> combine(a, b, c, d))));
中間オブジェクトを使用する必要があると思いますが、Pair
とTuple
を使用する代わりに、独自のオブジェクトを使用してください
public R method() {
CompletableFuture<A> aFuture = getAFuture();
CompletableFuture<B> bFuture = getBFuture();
CompletableFuture<C> cFuture = getCFuture();
CompletableFuture<D> dFuture = getDFuture();
return CompletableFuture.completedFuture(new WellNamedResultHolder())
.thenCombineAsync(aFuture, WellNamedResultHolder::withAResult)
.thenCombineAsync(bFuture, WellNamedResultHolder::withBResult)
.thenCombineAsync(cFuture, WellNamedResultHolder::withCResult)
.thenCombineAsync(dFuture, WellNamedResultHolder::withDResult)
.thenApplyAsync(this::combineAllTheResults);
}
private static class WellNamedResultHolder {
private A aResult;
private B bResult;
private C cResult;
private D dResult;
// Getters
public WellNamedResultHolder withAResult(final A aResult) {
this.aResult = aResult;
return this;
}
public WellNamedResultHolder withBResult(final B bResult) {
this.bResult = bResult;
return this;
}
public WellNamedResultHolder withCResult(final C cResult) {
this.cResult = cResult;
return this;
}
public WellNamedResultHolder withDResult(final D dResult) {
this.dResult = dResult;
return this;
}
}
結果ホルダーの実際の形式は明らかにユーザーのニーズに合わせて変更できるため、柔軟性が向上します。また、これらの先物が完了すると何が起こるかを担当することになります。ボイラープレートはもっとありますが、何が起こっているかをより詳しく説明するコードを取得します(ロンボクは整頓できます)。
「3以上」について尋ねましたが、CompletableFuturesとしてリストにある場合(他の回答を参照)、この便利な方法を使用できます。
private static <T> CompletableFuture<List<T>> join(List<CompletableFuture<T>> executionPromises) {
CompletableFuture<Void> joinedPromise = CompletableFuture.allOf(executionPromises.toArray(CompletableFuture[]::new));
return joinedPromise.thenApply(voit -> executionPromises.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}
それはあなたの「先物のリスト」を「将来の結果リスト」に変換します。
私は同様の問題を抱えていましたが、3つ以上の完全な未来があるので Holger's の答えに基づいて構築しました小さな汎用ユーティリティを作成しました。
public static <T, R> CompletableFuture<R> allOf(List<CompletableFuture<T>> args, Function<List<T>, R> combiner) {
final Queue<CompletableFuture<T>> queue = new LinkedList<>();
for (CompletableFuture<T> arg : args) {
queue.add(arg);
}
return aggregator(queue, new ArrayList<>(), combiner);
}
private static <T, R> CompletableFuture<R> aggregator(Queue<CompletableFuture<T>> queue, List<T> arg,
Function<List<T>, R> combiner) {
if (queue.size() == 2)
return queue.poll().thenCombine(queue.poll(), (c, d) -> {
arg.add(c);
arg.add(d);
return combiner.apply(arg);
});
return queue.poll().thenCompose(data -> {
arg.add(data);
return aggregator(queue, arg, combiner);
});
}
任意の数のCompletableFutureを組み合わせることができます(削減)
CompletionStage<A> futA = getA();
CompletionStage<B> futB = getB();
CompletionStage<C> futC = getC();
Stream.of(futA, futB, futC)
.reduce((f1, f2) -> f1.thenCombine(f2, (d1, d2) -> combine(d1, d2));
combineメソッドの実装は、データ値(A、B、およびC)をマージする責任があります。これは、A、B、およびCが異なる場合は注意が必要です。