[英]Java 8 Supplier Exception handling with CompletableFuture
Consider the following code考虑以下代码
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.
}
}
The above thing works fine.上面的东西工作正常。 However
sendNumbers
could also throw a checked exception in my case, like:但是,在我的情况下,
sendNumbers
也可能抛出已检查的异常,例如:
class SupplyNumbers {
public static Integer sendNumbers() throws Exception {
return 25; // just for working sake its not correct.
}
}
Now I want to handle this exception as y
in my biConsumer
.现在我想在我的
biConsumer
将此异常作为y
处理。 This will help me in handling the result as well as exception (if any) inside a single function ( biConsumer
).这将帮助我处理单个函数(
biConsumer
)内的结果和异常(如果有)。
Any ideas?有任何想法吗? Can I use
CompletableFuture.exceptionally(fn)
here or anything else?我可以在这里使用
CompletableFuture.exceptionally(fn)
或其他任何东西吗?
The factory methods using the standard functional interfaces aren't helpful when you want to handle checked exceptions.当您想要处理检查异常时,使用标准功能接口的工厂方法没有帮助。 When you insert code catching the exception into the lambda expression, you have the problem that the catch clause needs the
CompletableFuture
instance to set the exception while the factory method needs the Supplier
, chicken-and-egg.当您将捕获异常的代码插入到 lambda 表达式中时,您会遇到问题,即 catch 子句需要
CompletableFuture
实例来设置异常,而工厂方法需要Supplier
,鸡和蛋。
You could use an instance field of a class to allow mutation after creation, but in the end, the resulting code isn't clean and more complicated that a straight-forward Executor
-based solution.您可以使用类的实例字段在创建后允许突变,但最终,生成的代码并不干净,而且比基于
Executor
的直接解决方案更复杂。 The documentation of CompletableFuture
says: CompletableFuture
的文档说:
- All async methods without an explicit Executor argument are performed using the
ForkJoinPool.commonPool()
…所有没有显式 Executor 参数的异步方法都使用
ForkJoinPool.commonPool()
...
So you know the following code will show the standard behavior of CompletableFuture.supplyAsync(Supplier)
while handling checked exceptions straight-forward:因此,您知道以下代码将显示
CompletableFuture.supplyAsync(Supplier)
的标准行为,同时直接处理已检查的异常:
CompletableFuture<Integer> f=new CompletableFuture<>();
ForkJoinPool.commonPool().submit(()-> {
try { f.complete(SupplyNumbers.sendNumbers()); }
catch(Exception ex) { f.completeExceptionally(ex); }
});
The documentation also says:文档还说:
… To simplify monitoring, debugging, and tracking, all generated asynchronous tasks are instances of the marker interface
CompletableFuture.AsynchronousCompletionTask
.… 为了简化监控、调试和跟踪,所有生成的异步任务都是标记接口
CompletableFuture.AsynchronousCompletionTask
实例。
If you want to adhere to this convention to make the solution even more behaving like the original supplyAsync
method, change the code to:如果您想遵守此约定以使解决方案更像原始
supplyAsync
方法,请将代码更改为:
CompletableFuture<Integer> f=new CompletableFuture<>();
ForkJoinPool.commonPool().submit(
(Runnable&CompletableFuture.AsynchronousCompletionTask)()-> {
try { f.complete(SupplyNumbers.sendNumbers()); }
catch(Exception ex) { f.completeExceptionally(ex); }
});
You are already catching the exception in y
.您已经在
y
捕获异常。 Maybe you did not see it because main
exited before your CompletableFuture had a chance to complete?也许您没有看到它,因为
main
在您的 CompletableFuture 有机会完成之前退出了?
The code below prints "null" and "Hello" as expected:下面的代码按预期打印“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");
}
}
I am not quite sure what you are trying to achieve.我不太确定你想要实现什么。 If your supplier throws an exception, when you call
testFuture .get()
you will get java.util.concurrent.ExecutionException
caused by any exception that was thrown by the supplier, that you can retrieve by calling getCause()
on ExecutionException
.如果您的供应商抛出异常,当您调用
testFuture .get()
您将获得由供应商抛出的任何异常引起的java.util.concurrent.ExecutionException
,您可以通过在ExecutionException
上调用getCause()
来检索该异常。
Or, just as you mentioned, you can use exceptionally
in the CompletableFuture
.或者,正如您提到的,您可以在
CompletableFuture
exceptionally
使用。 This code:这段代码:
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());
}
}
Prints this result:打印此结果:
null
java.util.concurrent.CompletionException: java.lang.RuntimeException
result = 7
EDIT:编辑:
If you have checked exceptions, you can simply add a try-catch.如果您有检查过的异常,您可以简单地添加一个 try-catch。
Original code:原始代码:
Supplier<Integer> numberSupplier = new Supplier<Integer>() {
@Override
public Integer get() {
return SupplyNumbers.sendNumbers();
}
};
Modified code:修改后的代码:
Supplier<Integer> numberSupplier = new Supplier<Integer>() {
@Override
public Integer get() {
try {
return SupplyNumbers.sendNumbers();
} catch (Excetpion e) {
throw new RuntimeExcetpion(e);
}
}
};
Perhaps you could use new Object to wrap your integer and error like this:也许您可以使用 new Object 来包装您的整数和错误,如下所示:
public class Result {
private Integer integer;
private Exception exception;
// getter setter
}
And then:进而:
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);
}
CompletionException
CompletionException
Another point to take into account with exception handling in CompletableFuture
when using completeExceptionally()
is that the exact exception will be available in handle()
and whenComplete()
but it will be wrapped in CompletionException
when calling join()
or when it is forwarded to any downstream stage.还有一点要考虑到在异常处理
CompletableFuture
使用时completeExceptionally()
是准确的异常将是可用handle()
和whenComplete()
但它会被包裹在CompletionException
致电时join()
或当它被转发到任何下游阶段。
A handle()
or exceptionally()
applied to a downstream stage will thus see a CompletionException
instead of the original one, and will have to look at its cause to find the original exception.因此,应用于下游阶段的
handle()
或exceptionally()
将看到CompletionException
而不是原始异常,并且必须查看其原因以找到原始异常。
Moreover, any RuntimeException
thrown by any operation (including supplyAsync()
) is also wrapped in a CompletionException
, except if it is already a CompletionException
.此外,任何操作(包括
supplyAsync()
)抛出的任何RuntimeException
也包含在CompletionException
,除非它已经是CompletionException
。
Considering this, it is better to play it on the safe side and have your exception handlers unwrap the CompletionException
s.考虑到这一点,最好在安全方面进行播放,并让您的异常处理程序解开
CompletionException
s。
If you do that, there is no point anymore to set the exact (checked) exception on the CompletableFuture
and it is much simpler to wrap checked exceptions in CompletionException
directly :如果你这样做,就没有必要在
CompletableFuture
上设置确切的(已检查的)异常,并且直接在CompletionException
包装已检查的异常要简单得多:
Supplier<Integer> numberSupplier = () -> {
try {
return SupplyNumbers.sendNumbers();
} catch (Exception e) {
throw new CompletionException(e);
}
};
To compare this approach with Holger's approach , I adapted your code with the 2 solutions ( simpleWrap()
is the above, customWrap()
is Holger's code):为了将这种方法与Holger 的方法进行比较,我使用 2 个解决方案调整了您的代码
simpleWrap()
上面是customWrap()
, customWrap()
是 Holger 的代码):
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.
}
}
Output:输出:
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
As you'll notice, the only difference is that the whenComplete()
sees the original exception before thenApply()
in the customWrap()
case.正如您看到的,唯一的区别是,
whenComplete()
看到原始异常之前thenApply()
在customWrap()
情况。 After thenApply()
, and in all other cases, the original exception is wrapped.在
thenApply()
,在所有其他情况下,原始异常被包装。
The most surprising thing is that get()
will unwrap the CompletionException
in the "Simple wrap" case, and replace it with an ExecutionException
.最令人惊讶的是
get()
会在“简单包装”的情况下解开CompletionException
,并将其替换为ExecutionException
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.