如何转换代码以使用CompletableFuture?

我以前有一个可调用的类

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

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

以下代码无法编译

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

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

supplyAsync()期望Supplier并且您给它一个Callable

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

Java将隐式地将lambda“推广”到function接口,例如CallableSupplier 。 但它不会将function接口视为可互换的 – 也就是说,您不能在需要Supplier地方使用Callable

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

 SimpleTask task = new SimpleTask(); CompletableFuture.supplyAsync(() -> task.call()); 

请注意,如果SimpleTaskcall()是:

  public Double call() { // note no exception declared return 0d; } 

SimpleTask恰好实现Callable的事实与上面的代码无关。


如果您希望这可以使用任意Callable ,或者您将task声明为Callable

 Callable callable = new SimpleTask(); CompletableFuture.supplyAsync(() -> callable.call()); 

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


或者您可以使SampleTask实现Supplier


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

 CompleteableFuture future = CompletableFuture.supplyAsync(() -> 0d); 

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

 CompleteableFuture future = CompletableFuture.supplyAsync(() -> { Foo foo = slowQuery(); return transformToDouble(foo); }); 

对于你所编写的callable,你可以简单地使用CompletableFuture.supplyAsync(() -> 0d);

但是,如果你有一个现有的Callable ,那么将它与CompletableFuture一起使用并不是那么简单,因为可调用的exception可能会抛出。

您可以使用ad-hoc Supplier捕获exception并重新抛出exception,例如,包含在未经检查的exception中

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

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

但是,当您将exception处理程序链接到CompletableFuture时,您会注意到包装。 此外, join()抛出的CompletionException将是catch子句中创建的CompletionException ,因此包含一些后台线程的堆栈跟踪,而不是调用join()的线程。 换句话说,行为仍然不同于抛出exception的Supplier

使用稍微复杂一些

 public static  CompletableFuture callAsync(Callable callable) { CompletableFuture cf = new CompletableFuture<>(); CompletableFuture.runAsync(() -> { try { cf.complete(callable.call()); } catch(Throwable ex) { cf.completeExceptionally(ex); } }); return cf; } 

你得到一个CompletableFuture ,其行为与supplyAsync完全相同,没有额外的包装器exception类型,即如果你使用的话

 callAsync(task).exceptionally(t -> { t.printStackTrace(); return 42.0; }) 

t将是Callable抛出的确切exception(如果有的话),即使它是一个已检查的exception。 callAsync(task).join()也会产生一个CompletionException ,其中包含join()调用者的堆栈跟踪,直接包装Callable在exception情况下抛出的exception,就像与SupplierrunAsync

由于CompleteableFuture::supplyAsync需要Supplier而不能Callable您应该使用:

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