連鎖したいいくつかのCompletionStage
メソッドがあります。問題は、最初のものの結果が次のものを実行すべきかどうかを決定することです。現在、これを実現する唯一の方法は、「特別な」引数をnext CompletionStage
に渡すことです。そのため、完全なコードは実行されません。例えば:
_public enum SomeResult {
RESULT_1,
RESULT_2,
RESULT_3
}
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
return CompletableFuture.supplyAsync(() -> {
// loooooong operation
if (someCondition)
return validValue;
else
return null;
}).thenCompose(result -> {
if (result != null)
return someMethodThatReturnsACompletionStage(result);
else
return CompletableFuture.completedFuture(null);
}).thenApply(result -> {
if (result == null)
return ChainingResult.RESULT_1;
else if (result.someCondition())
return ChainingResult.RESULT_2;
else
return ChainingResult.RESULT_3;
});
}
_
コード全体が最初のsomeCondition
に依存しているため(それがfalse
の場合、結果は_RESULT_1
_になります。そうでない場合は、コード全体を実行する必要があります)、この構成は少し見苦しく見えます私に。 2番目の(thenCompose(...)
)および3番目の(thenApply(...)
)メソッドを実行する必要があるかどうかを判断する方法はありますか?
あなたはこのようにそれを行うことができます:
_public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<SomeResult> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(SomeResult.RESULT_1);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.thenApply(result ->
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3)
.applyToEither(shortCut, Function.identity());
}
_
1つのCompletableFuture
ではなく、2つのパスを表す2つのパスを作成します。 loooooong操作は実行可能として送信され、これらのCompletableFuture
の1つを意図的に完了します。フォローアップステージは、満たされた条件を表すステージにチェーンされ、両方の実行パスが最後のapplyToEither(shortCut, Function.identity())
ステップで結合します。
shortCut
futureにはすでに最終結果のタイプがあり、null
passingパスの結果である_RESULT_1
_で完了します。これにより、操作全体が即座に完了します。最初のステージとショートカットの実際の結果値の間の依存関係が気に入らない場合は、次のように撤回できます。
_public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<Object> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(null);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.thenApply(result ->
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3)
.applyToEither(shortCut.thenApply(x -> SomeResult.RESULT_1), Function.identity());
}
_
3番目のステップが典型的ではなかったが、質問に示されているように見える場合は、コードパスの結合ステップとマージできます。
_public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
CompletableFuture<ResultOfSecondOp> shortCut = new CompletableFuture<>();
CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
// loooooong operation
if (someCondition)
withChain.complete(validValue);
else
shortCut.complete(null);
});
return withChain
.thenCompose(result -> someMethodThatReturnsACompletionStage(result))
.applyToEither(shortCut, result -> result==null? SomeResult.RESULT_1:
result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3);
}
_
次に、2番目のステップであるsomeMethodThatReturnsACompletionStage
呼び出しのみをスキップしますが、それでも、nullcheckを使用して手動でスキップすることなく、すべてスキップされた中間ステップの長いチェーンを表すことができます。
完全を期すために、新しい答えを追加します
@Holgerによって提案されたソリューションはうまく機能しますが、それは私にはちょっと奇妙です。私が使用しているソリューションには、異なるメソッド呼び出しで異なるフローを分離し、それらをthenCompose
でチェーンすることが含まれます。
public enum SomeResult {
RESULT_1,
RESULT_2,
RESULT_3
}
public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
return CompletableFuture.supplyAsync(() -> {
// loooooong operation
if (someCondition)
return operateWithValidValue(value);
else
return CompletableFuture.completedValue(ChainingResult.RESULT_1);
})
.thenCompose(future -> future);
public CompletionStage<SomeResult> operateWithValidValue(... value) {
// more loooong operations...
if (someCondition)
return CompletableFuture.completedValue(SomeResult.RESULT_2);
else
return doFinalOperation(someOtherValue);
}
public CompletionStage<SomeResult> doFinalOperation(... value) {
// more loooong operations...
if (someCondition)
return CompletableFuture.completedValue(SomeResult.RESULT_2);
else
return CompletableFuture.completedValue(SomeResult.RESULT_3);
}
[〜#〜] note [〜#〜]:より完全な回答を得るために、アルゴリズムを質問から変更しました
すべての長い操作は、わずかな労力で別のCompletableFuture.supplyAsync
にラップされる可能性があります
Null値のみをチェックする必要がある場合は、Optional
を使用して解決できます。たとえば、次のようにする必要があります。
_public Bar execute(String id) {
return this.getFooById(id)
.thenCompose(this::checkFooPresent)
.thenCompose(this::doSomethingElse)
.thenCompose(this::doSomethingElseMore)
.thenApply(rankRes -> new Bar(foo));
}
private Optional<Foo> getFooById(String id) {
// some better logic to retrieve foo
return Optional.ofNullable(foo);
}
private CompletableFuture<Foo> checkFooPresent(Optional<Foo> optRanking) {
CompletableFuture<Foo> future = new CompletableFuture();
optRanking.map(future::complete).orElseGet(() -> future.completeExceptionally(new Exception("Foo not present")));
return future;
}
_
checkFooPresent()
はOptional
を受け取り、その値がnull
の場合、例外的にCompletableFuture
を完了します。
当然、その例外を管理する必要がありますが、以前にExceptionHandler
または同様の何かを設定したことがある場合は、無料で提供されるはずです。