如何将代码转换为使用CompletableFuture?

时间:2017-05-03 15:16:46

标签: java java-8

我曾经有一个可调用的类

class SampleTask implements Callable<Double> {
  @Override
  public Double call() throws Exception {
    return 0d;
  }
}

我曾使用ExecutorService提交Callable。如何更改为使用CompletableFuture.supplyAsync

以下代码无法编译

SampleTask task = new SampleTask();
CompletableFuture.supplyAsync(task);
  

不存在变量类型U的实例,因此SampleTask符合供应商

4 个答案:

答案 0 :(得分:6)

对于您编写的可调用内容,您只需使用CompletableFuture.supplyAsync(() -> 0d);

但是,如果您有一个Callable,那么将其与CompletableFuture一起使用并不是那么简单,因为可调用的异常可能会抛出。

你可以使用一个特殊的Supplier来捕获异常并重新抛出它包含在未经检查的异常中,如

CompletableFuture.supplyAsync(() -> {
    try { return callable.call(); }
    catch(Exception e) { throw new CompletionException(e); }
})

使用特定类型CompletionException而不是RuntimeException的任意子类型,可以避免在调用CompletionException时包装一个包含实际异常的join()运行时异常。

但是,当您将异常处理程序链接到CompletableFuture时,您会注意到包装。此外,CompletionException抛出的join()将是catch子句中创建的join(),因此包含一些后台线程的堆栈跟踪,而不是调用Supplier的线程。换句话说,行为仍然不同于抛出异常的public static <R> CompletableFuture<R> callAsync(Callable<R> callable) { CompletableFuture<R> cf = new CompletableFuture<>(); CompletableFuture.runAsync(() -> { try { cf.complete(callable.call()); } catch(Throwable ex) { cf.completeExceptionally(ex); } }); return cf; }

使用稍微复杂的

CompletableFuture

你得到supplyAsync,其行为与callAsync(task).exceptionally(t -> { t.printStackTrace(); return 42.0; }) 完全相同,没有其他包装器异常类型,即使用

t

Callable将是callAsync(task).join()抛出的确切异常(如果有),即使它是已检查的异常。此外,CompletionException将生成join(),其中Callable调用者的堆栈跟踪直接包装Supplier在异常情况下抛出的异常,与{runAsync完全相同1}}或类似于expand: function(field, opts) { field.picker.activeDate = new Date(); field.picker.update(null, false); }

答案 1 :(得分:3)

supplyAsync()预计会Supplier<U>而你正在给它Callable

错误消息告诉您编译器已尝试查找要用于U的类型,以使您的SampleTask“为”Supplier<U>,但找不到之一。

Java会隐式地将lambda“推广”到功能界面,例如CallableSupplier。但它不会将功能接口视为可互换的 - 也就是说,您不能使用期望Callable的{​​{1}}。

你可以在适当的位置制作合适的lambda:

Supplier

请注意,如果SimpleTask task = new SimpleTask(); CompletableFuture.supplyAsync(() -> task.call()); 的{​​{1}}为:

,则此方法有效
SimpleTask

call()恰好实施 public Double call() { // note no exception declared return 0d; } 的事实与上述代码无关。

如果您希望将此工作与任意SimpleTask一起使用,或者将Callable声明为Callable

task

...然后您将收到有关未捕获异常的编译器错误。你的lambda将需要捕获异常并处理它(可能重新抛出为未经检查的异常,如其他答案中所述)。

或者您可以Callable实施Callable callable = new SimpleTask(); CompletableFuture.supplyAsync(() -> callable.call());

lambdas的部分动机是写SampleTask这样的东西太冗长了。所以你可能会遗漏中间类并直接进入:

Supplier<Double>

这也适用于更复杂的供应商:

Callable

答案 2 :(得分:1)

由于CompleteableFuture::supplyAsync期望Supplier<Double>而不是Callable<Double>,您应该选择:

Callable<Double> task = new SampleTask();
CompletableFuture.supplyAsync(() -> {
  try {
    return task.call();
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
});

答案 3 :(得分:0)

我最近遇到了这个问题,并使用Vavr来解决它(也已经将它用于其他用途),对我来说非常有用:

CompletableFuture.supplyAsync( () -> Try.ofCallable( callable ).get() )

或获取该CompletableFuture的供应商:

() -> CompletableFuture.supplyAsync( () -> Try.ofCallable( callable ).get() )

在所有情况下,我都测试了此结果是否完全返回并完全抛出了可调用对象本身的操作。