请考虑以下代码
public class TestCompletableFuture {
BiConsumer<Integer, Throwable> biConsumer = (x,y) -> {
System.out.println(x);
System.out.println(y);
};
public static void main(String args[]) {
TestCompletableFuture testF = new TestCompletableFuture();
testF.start();
}
public void start() {
Supplier<Integer> numberSupplier = new Supplier<Integer>() {
@Override
public Integer get() {
return SupplyNumbers.sendNumbers();
}
};
CompletableFuture<Integer> testFuture = CompletableFuture.supplyAsync(numberSupplier).whenComplete(biConsumer);
}
}
class SupplyNumbers {
public static Integer sendNumbers(){
return 25; // just for working sake its not correct.
}
}
以上的事情很好。然而,sendNumbers
也可能在我的情况下抛出一个检查异常,如:
class SupplyNumbers {
public static Integer sendNumbers() throws Exception {
return 25; // just for working sake its not correct.
}
}
现在我想在我的y
中处理biConsumer
这个例外。这将帮助我在单个函数(biConsumer
)中处理结果以及异常(如果有的话)。
有任何想法吗?我可以在这里或其他任何地方使用CompletableFuture.exceptionally(fn)
吗?
当您想要处理已检查的异常时,使用标准功能接口的工厂方法没有帮助。当您将代码捕获到lambda表达式中时,您遇到的问题是catch子句需要CompletableFuture
实例来设置异常,而工厂方法需要Supplier
,chicken-and-egg。
您可以使用类的实例字段在创建后允许变异,但最终,生成的代码不是干净的,而且比基于Executor
的直接解决方案更复杂。 documentation of CompletableFuture
说:
- 所有没有显式Executor参数的异步方法都是使用
ForkJoinPool.commonPool()
执行的...
因此,您知道以下代码将直接处理检查异常时显示CompletableFuture.supplyAsync(Supplier)
的标准行为:
CompletableFuture<Integer> f=new CompletableFuture<>();
ForkJoinPool.commonPool().submit(()-> {
try { f.complete(SupplyNumbers.sendNumbers()); }
catch(Exception ex) { f.completeExceptionally(ex); }
});
文件还说:
...为了简化监视,调试和跟踪,所有生成的异步任务都是标记接口
CompletableFuture.AsynchronousCompletionTask
的实例。
如果您希望遵守此约定以使解决方案更像原始supplyAsync
方法,请将代码更改为:
CompletableFuture<Integer> f=new CompletableFuture<>();
ForkJoinPool.commonPool().submit(
(Runnable&CompletableFuture.AsynchronousCompletionTask)()-> {
try { f.complete(SupplyNumbers.sendNumbers()); }
catch(Exception ex) { f.completeExceptionally(ex); }
});
您已经在y
中捕获了异常。也许你没有看到它因为main
在CompletableFuture有机会完成之前退出了?
下面的代码按预期打印“null”和“Hello”:
public static void main(String args[]) throws InterruptedException {
TestCompletableFuture testF = new TestCompletableFuture();
testF.start();
Thread.sleep(1000); //wait for the CompletableFuture to complete
}
public static class TestCompletableFuture {
BiConsumer<Integer, Throwable> biConsumer = (x, y) -> {
System.out.println(x);
System.out.println(y);
};
public void start() {
CompletableFuture.supplyAsync(SupplyNumbers::sendNumbers)
.whenComplete(biConsumer);
}
}
static class SupplyNumbers {
public static Integer sendNumbers() {
throw new RuntimeException("Hello");
}
}
我不太确定你想要达到的目的。如果您的供应商抛出异常,当您致电testFuture .get()
时,您将获得由供应商抛出的任何异常引起的java.util.concurrent.ExecutionException
,您可以通过在getCause()
上调用ExecutionException
来检索。
或者,就像你提到的那样,你可以在exceptionally
中使用CompletableFuture
。这段代码:
public class TestCompletableFuture {
private static BiConsumer<Integer, Throwable> biConsumer = (x,y) -> {
System.out.println(x);
System.out.println(y);
};
public static void main(String args[]) throws Exception {
Supplier<Integer> numberSupplier = () -> {
throw new RuntimeException(); // or return integer
};
CompletableFuture<Integer> testFuture = CompletableFuture.supplyAsync(numberSupplier)
.whenComplete(biConsumer)
.exceptionally(exception -> 7);
System.out.println("result = " + testFuture.get());
}
}
打印此结果:
null
java.util.concurrent.CompletionException: java.lang.RuntimeException
result = 7
编辑:
如果您已检查异常,则只需添加try-catch即可。
原始代码:
Supplier<Integer> numberSupplier = new Supplier<Integer>() {
@Override
public Integer get() {
return SupplyNumbers.sendNumbers();
}
};
修改后的代码
Supplier<Integer> numberSupplier = new Supplier<Integer>() {
@Override
public Integer get() {
try {
return SupplyNumbers.sendNumbers();
} catch (Excetpion e) {
throw new RuntimeExcetpion(e);
}
}
};
也许您可以使用新的Object来包装整数和错误,如下所示:
public class Result {
private Integer integer;
private Exception exception;
// getter setter
}
然后:
public void start(){
Supplier<Result> numberSupplier = new Supplier<Result>() {
@Override
public Result get() {
Result r = new Result();
try {
r.setInteger(SupplyNumbers.sendNumbers());
} catch (Exception e){
r.setException(e);
}
return r;
}
};
CompletableFuture<Result> testFuture = CompletableFuture.supplyAsync(numberSupplier).whenComplete(biConsumer);
}
使用CompletableFuture
时completeExceptionally()
中的异常处理需要考虑的另一点是,handle()
和whenComplete()
中将提供确切的异常,但在调用CompletionException
或转发到任何下游阶段时,它将包含在join()
中。
因此,应用于下游阶段的handle()
或exceptionally()
将看到CompletionException
而不是原始的RuntimeException
,并且必须查看其原因以找到原始异常。
此外,任何操作(包括supplyAsync()
)抛出的任何CompletionException
也包裹在CompletionException
中,除非它已经是CompletionException
。
考虑到这一点,最好是在安全的方面进行游戏并让你的异常处理程序解开CompletableFuture
s。
如果你这样做,就没有必要在CompletionException
上设置确切的(已检查的)异常,并且直接在Supplier<Integer> numberSupplier = () -> {
try {
return SupplyNumbers.sendNumbers();
} catch (Exception e) {
throw new CompletionException(e);
}
};
中包装已检查的异常要简单得多:
Holger's approach
为了比较这种方法与simpleWrap()
,我使用2个解决方案调整了你的代码(customWrap()
是上面的,public class TestCompletableFuture {
public static void main(String args[]) {
TestCompletableFuture testF = new TestCompletableFuture();
System.out.println("Simple wrap");
testF.handle(testF.simpleWrap());
System.out.println("Custom wrap");
testF.handle(testF.customWrap());
}
private void handle(CompletableFuture<Integer> future) {
future.whenComplete((x1, y) -> {
System.out.println("Before thenApply(): " + y);
});
future.thenApply(x -> x).whenComplete((x1, y) -> {
System.out.println("After thenApply(): " + y);
});
try {
future.join();
} catch (Exception e) {
System.out.println("Join threw " + e);
}
try {
future.get();
} catch (Exception e) {
System.out.println("Get threw " + e);
}
}
public CompletableFuture<Integer> simpleWrap() {
Supplier<Integer> numberSupplier = () -> {
try {
return SupplyNumbers.sendNumbers();
} catch (Exception e) {
throw new CompletionException(e);
}
};
return CompletableFuture.supplyAsync(numberSupplier);
}
public CompletableFuture<Integer> customWrap() {
CompletableFuture<Integer> f = new CompletableFuture<>();
ForkJoinPool.commonPool().submit(
(Runnable & CompletableFuture.AsynchronousCompletionTask) () -> {
try {
f.complete(SupplyNumbers.sendNumbers());
} catch (Exception ex) {
f.completeExceptionally(ex);
}
});
return f;
}
}
class SupplyNumbers {
public static Integer sendNumbers() throws Exception {
throw new Exception("test"); // just for working sake its not correct.
}
}
是Holger的代码):
Simple wrap
After thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Before thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Join threw java.util.concurrent.CompletionException: java.lang.Exception: test
Get threw java.util.concurrent.ExecutionException: java.lang.Exception: test
Custom wrap
After thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Before thenApply(): java.lang.Exception: test
Join threw java.util.concurrent.CompletionException: java.lang.Exception: test
Get threw java.util.concurrent.ExecutionException: java.lang.Exception: test
输出:
whenComplete()
正如你会注意到的,唯一的区别是thenApply()
在customWrap()
案例中看到thenApply()
之前的原始例外。在get()
之后,在所有其他情况下,原始异常被包装。
最令人惊讶的是,CompletionException
将在“简单包装”的情况下打开ExecutionException
,并用qazxswpoi替换它。