仅在满足条件时链接多个 CompletionStage
Posted
技术标签:
【中文标题】仅在满足条件时链接多个 CompletionStage【英文标题】:Chaining several CompletionStage only if a condition is achieved 【发布时间】:2016-07-24 05:01:27 【问题描述】:我想链接几个CompletionStage
方法。问题是第一个的结果将决定是否应该执行下一个。现在实现这一点的唯一方法似乎是将“特殊”参数传递给下一个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
,如果不是,那么应该执行整个代码)这个结构对我来说有点难看。有没有办法决定是否应该执行第二个(thenCompose(...)
)和第三个(thenApply(...)
)方法?
【问题讨论】:
【参考方案1】:如果您只需要检查空值,您可以使用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
或类似的东西,它应该是免费的。
【讨论】:
【参考方案2】:为了完整起见,我添加了一个新答案
虽然@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);
注意:为了获得更完整的答案,我已经更改了问题中的算法
所有长操作都可以轻松包装在另一个 CompletableFuture.supplyAsync
中
【讨论】:
【参考方案3】:你可以这样做:
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());
我们创建两个而不是一个CompletableFuture
,代表我们可能采用的不同执行路径。 loooooong 操作被提交为可运行然后将故意完成其中一个CompletableFuture
。后续阶段链接到表示满足条件的阶段,然后两个执行路径在最后一个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());
如果您的第三步不是示例但看起来与问题中显示的完全一样,您可以将其与代码路径连接步骤合并:
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);
然后我们只跳过第二步,someMethodThatReturnsACompletionStage
调用,但这仍然可以代表一长串中间步骤,所有步骤都被跳过,而无需通过 nullcheck 推出手动跳过。
【讨论】:
它有效,谢谢!遵循相同的模式(创建多个CompletableFuture
并使用applyToEither(...)
)然后可以将其扩展到多个路径,对吗?
是的,您可以将其扩展到多个路径,但必须注意保持生成的代码可维护。也许它有助于将分支的逻辑封装到一个可以多次使用的实用方法中。以上是关于仅在满足条件时链接多个 CompletionStage的主要内容,如果未能解决你的问题,请参考以下文章