通常使用CompletableFuture,我会在结果可用时立即调用thenApply或其他方法来执行某些操作。但是,我现在有一种情况,我希望处理结果,直到我收到一个阳性结果,然后忽略所有进一步的结果。
如果我只想获得第一个可用的结果,我可以使用CompletableFuture.anyOf(虽然我讨厌将列表转换为数组只是为了调用anyOf)。但这不是我想要的。我想取第一个结果,如果它没有理想的结果,那么我想处理第二个可用的结果,依此类推,直到得到理想的结果。
这是一个简单的例子,它遍历所有结果并返回它找到的大于9的第一个值。(请注意,这不是我真正的任务。这只是一个简单的例子。)
public Integer findFirstGt9(List<CompletableFuture<Integer>> results) {
for(CompletableFuture<Integer> result : results) {
Integer v = result.get();
if(v > 9)
return v;
}
return null;
}
当然,这个例子从一开始就经历了结果,而不是在结果完成时查看结果。所以这里有一个可以实现我想要的东西,但代码要复杂得多。
public Integer findFirstGt9(List<CompletableFuture<Integer>> results) {
AtomicInteger finalResult = new AtomicInteger();
CountDownLatch latch = new CountDownLatch(results.size());
for(CompletableFuture<Integer> result : results) {
result.whenComplete((v,e) -> {
if(e!=null) {
Logger.getLogger(getClass()).error("",e);
} else if(v > 9) {
finalResult.set(v);
while(latch.getCount() > 0)
latch.countDown();
return;
}
latch.countDown();
});
}
latch.await();
if(finalResult.get() > 9)
return finalResult.get();
return null;
}
我能做到这样的api吗?
public Integer findFirstGt9(List<CompletableFuture<Integer>> results) {
Iterator<Integer> resultIt = getResultsAsAvailable(results);
for(; resultIt.hasNext();) {
Integer v = resultIt.next();
if(v > 9)
return v;
}
return null;
}
甚至更好:
public Integer findFirstGt9(List<CompletableFuture<Integer>> results) {
return getFirstMatch(results, r -> {return r > 9;});
}
答案 0 :(得分:3)
我不知道JDK或其他地方的任何此类API。你可以自己动手。
如果未来已经完成,您可以利用CompletableFuture#complete
(和completeExceptionally
)无效的事实。
如果尚未完成,请设置
get()
返回的值并相关 给定值的方法。
创建新的最终结果 CompletableFuture
。如果您的条件适用,请为尝试complete
此最终结果的每个期货添加续约。未来将取得第一次成功。但是,如果没有成功,您显然需要null
。您可以使用CompletableFuture
创建allOf
,并使用complete
尝试null
最终结果。
像
这样的东西public static <T> CompletableFuture<T> firstOrNull(List<CompletableFuture<T>> futures, Predicate<T> condition) {
CompletableFuture<T> finalResult = new CompletableFuture<>();
// attempt to complete on success
futures.stream().forEach(future -> future.thenAccept(successResult -> {
if (condition.test(successResult))
finalResult.complete(successResult);
}));
CompletableFuture<?> all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
all.thenRun(() -> {
finalResult.complete(null);
});
return finalResult;
}
您支付无操作调用的开销。
您可以根据需要将null
更改为某个默认值,或者在发生错误时以不同方式处理异常(completeExceptionally
)。您必须使用whenComplete
或handle
代替上面的thenAccept
才能访问Exception
。
答案 1 :(得分:2)
您可以使用以下解决方案:
public static <T> CompletableFuture<T> anyMatch(
List<? extends CompletionStage<? extends T>> l, Predicate<? super T> criteria) {
CompletableFuture<T> result=new CompletableFuture<>();
Consumer<T> whenMatching=v -> { if(criteria.test(v)) result.complete(v); };
CompletableFuture.allOf(l.stream()
.map(f -> f.thenAccept(whenMatching)).toArray(CompletableFuture<?>[]::new))
.whenComplete((ignored, t) ->
result.completeExceptionally(t!=null? t: new NoSuchElementException()));
return result;
}
基本原则与Pillar’s answer中的基本原则相同,但存在一些差异:
CompletableFuture.allOf
所需的数组的创建与对源期货的后续行动的注册相结合。作为副作用,allOf
动作的处理程序取决于完成结果的所有尝试的完成,而不是仅仅是原始期货。这使得实际期望的依赖性显式化。这样,当我们用thenAccept
s替换所有thenAcceptAsync
时,它甚至会起作用。NoSuchElementException
完成,而不是在没有结果符合条件的情况下返回null
。如果至少有一个未来异常完成且没有成功完成匹配结果,则会传递其中一个发生的异常。您可以尝试
List<CompletableFuture<Integer>> list=Arrays.asList(
CompletableFuture.supplyAsync(()->5),
CompletableFuture.supplyAsync(()->{throw new RuntimeException(); }),
CompletableFuture.supplyAsync(()->42),
CompletableFuture.completedFuture(0)
);
anyMatch(list, i -> i>9)
.thenAccept(i->System.out.println("got "+i))
// optionally chain with:
.whenComplete((x,t)->{ if(t!=null) t.printStackTrace(); });