[英]Java 8 Supplier Exception handling with CompletableFuture
考虑以下代码
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.
}
}
现在我想在我的biConsumer
将此异常作为y
处理。 这将帮助我处理单个函数( biConsumer
)内的结果和异常(如果有)。
有任何想法吗? 我可以在这里使用CompletableFuture.exceptionally(fn)
或其他任何东西吗?
当您想要处理检查异常时,使用标准功能接口的工厂方法没有帮助。 当您将捕获异常的代码插入到 lambda 表达式中时,您会遇到问题,即 catch 子句需要CompletableFuture
实例来设置异常,而工厂方法需要Supplier
,鸡和蛋。
您可以使用类的实例字段在创建后允许突变,但最终,生成的代码并不干净,而且比基于Executor
的直接解决方案更复杂。 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
,您可以通过在ExecutionException
上调用getCause()
来检索该异常。
或者,正如您提到的,您可以在CompletableFuture
exceptionally
使用。 这段代码:
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);
}
}
};
也许您可以使用 new 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);
}
CompletionException
还有一点要考虑到在异常处理CompletableFuture
使用时completeExceptionally()
是准确的异常将是可用handle()
和whenComplete()
但它会被包裹在CompletionException
致电时join()
或当它被转发到任何下游阶段。
因此,应用于下游阶段的handle()
或exceptionally()
将看到CompletionException
而不是原始异常,并且必须查看其原因以找到原始异常。
此外,任何操作(包括supplyAsync()
)抛出的任何RuntimeException
也包含在CompletionException
,除非它已经是CompletionException
。
考虑到这一点,最好在安全方面进行播放,并让您的异常处理程序解开CompletionException
s。
如果你这样做,就没有必要在CompletableFuture
上设置确切的(已检查的)异常,并且直接在CompletionException
包装已检查的异常要简单得多:
Supplier<Integer> numberSupplier = () -> {
try {
return SupplyNumbers.sendNumbers();
} catch (Exception e) {
throw new CompletionException(e);
}
};
为了将这种方法与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.
}
}
输出:
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
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.