提问者:小点点

CompletableFuture#异常重新抛出检查异常


假设我想在收到特定异常时恢复某个值,否则返回失败的未来。我希望是这样的:

public static void main(String[] args) {
    CompletableFuture
            .supplyAsync(FuturesExample::fetchValue)
            .exceptionally(throwable -> {
                if (throwable instanceof RuntimeException) {
                    return "All good";
                }
                throw throwable; // does not compile
            });
}

public static String fetchValue() {
    // code that potentially throws an exception
    return "value";
}

如果fetchValue函数会抛出检查过的异常,我想在链式方法中处理它。我尝试过back throwable抛出throwable,但都无法编译。CompletableFuture是否为这种情况提供了任何解决方案?我知道Function接口是异常方法的参数,它不会抛出任何异常——在这种情况下,我只想返回已经失败的未来。我想找到使用Java8的解决方案。


共2个答案

匿名用户

在这种情况下,不可能收到已检查的异常,因为前一阶段基于供应商,不允许抛出已检查的异常。

因此,您可以处理所有未检查的异常并为应该是不可能的throwable引发AssertionError

CompletableFuture
    .supplyAsync(FuturesExample::fetchValue)
    .exceptionally(throwable -> {
        if (throwable instanceof RuntimeException) {
            return "All good";
        }
        if(throwable instanceof Error) throw (Error)throwable;
        throw new AssertionError(throwable);
    });

否则,您可能会认为后续阶段以及 join() 的调用方将获得除“完成异常”和“取消异常”之外的所有异常都包装在“完成异常”中。例如,当我使用

public static void main(String[] args) {
    CompletableFuture<String> f = CompletableFuture
        .supplyAsync(FuturesExample::fetchValue)
        .exceptionally(throwable -> {
            if(throwable instanceof RuntimeException) {
                throw (RuntimeException)throwable;
            }
            throw new Error();
        });
    f.whenComplete((s,t) -> {
        if(t != null) {
            System.err.println("in whenComplete handler ");
            t.printStackTrace();
        }
    });
    System.err.println("calling join()");
    f.join();
}
public static String fetchValue() {
    throw new IllegalStateException("a test is going on");
}

我明白了

in whenComplete handler 
java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
    at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
    at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.IllegalStateException: a test is going on
    at FuturesExample.fetchValue(FuturesExample.java:23)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
    ... 6 more
calling join()
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
    at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
    at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.IllegalStateException: a test is going on
    at FuturesExample.fetchValue(FuturesExample.java:23)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
    ... 6 more

因此,我可以使用< code>CompletionException来包装任意的throwables,利用< code>CompletionException不会被再次包装的事实。所以如果我用

public static void main(String[] args) {
    CompletableFuture<String> f = CompletableFuture
        .supplyAsync(FuturesExample::fetchValue)
        .exceptionally(throwable -> {
            if(throwable instanceof CompletionException)
                throwable = throwable.getCause();
            System.err.println("wrapping '"+throwable+"' inside exceptionally");
            throw new CompletionException(throwable);
        });
    f.whenComplete((s,t) -> {
        if(t != null) {
            System.err.println("in whenComplete handler ");
            t.printStackTrace();
        }
    });
    System.err.println("calling join()");
    f.join();
}
public static String fetchValue() {
    throw new IllegalStateException("a test is going on");
}

我明白了

wrapping 'java.lang.IllegalStateException: a test is going on' inside exceptionally
in whenComplete handler 
java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
    at FuturesExample.lambda$main$0(FuturesExample.java:12)
    at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:986)
    at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:970)
    at java.base/java.util.concurrent.CompletableFuture.unipush(CompletableFuture.java:589)
    at java.base/java.util.concurrent.CompletableFuture.uniExceptionallyStage(CompletableFuture.java:1002)
    at java.base/java.util.concurrent.CompletableFuture.exceptionally(CompletableFuture.java:2307)
    at FuturesExample.main(FuturesExample.java:8)
Caused by: java.lang.IllegalStateException: a test is going on
    at FuturesExample.fetchValue(FuturesExample.java:24)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
calling join()
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
    at FuturesExample.lambda$main$0(FuturesExample.java:12)
    at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:986)
    at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:970)
    at java.base/java.util.concurrent.CompletableFuture.unipush(CompletableFuture.java:589)
    at java.base/java.util.concurrent.CompletableFuture.uniExceptionallyStage(CompletableFuture.java:1002)
    at java.base/java.util.concurrent.CompletableFuture.exceptionally(CompletableFuture.java:2307)
    at FuturesExample.main(FuturesExample.java:8)
Caused by: java.lang.IllegalStateException: a test is going on
    at FuturesExample.fetchValue(FuturesExample.java:24)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)

这在堆栈跟踪中略有不同,但对接收/捕获异常的代码没有影响,因为在任何一种情况下,它都是包装IllegalStateExceptionCompletionException

回到你的问题的例子,你可以使用

CompletableFuture
    .supplyAsync(FuturesExample::fetchValue)
    .exceptionally(throwable -> {
        if (throwable instanceof RuntimeException) { // includes CompletionException
            return "All good";
        }
        throw new CompletionException(throwable);
    });

由于CompletionExceptionRuntimeException,因此此代码处理它并避免将CompletionException包装在另一个CompletionException中。否则,模式将是

    .exceptionally(throwable -> {
        if (some condition) {
            return some value;
        }
        throw throwable instanceof CompletionException?
            (CompletionException)throwable: new CompletionException(throwable);
    });

匿名用户

正如霍尔格所写,这通常是不可能的
但是,lombok有一个技巧,它是@SneakyThrows。

public static void main(String[] args) {
    CompletableFuture
            .supplyAsync(FuturesExample::fetchValue)
            .exceptionally(throwable -> {
                if (throwable instanceof RuntimeException) {
                    return "All good";
                }
                FutureExample.reThrow(throwable);
                // maybe a "return null" is necessary here (even when it is not reachable)
            });
}

public static String fetchValue() {
    // code that potentially throws an exception
    return "value";
}

@SneakyThrows // <- threat checked exceptions in method-body as unchecked
public static void reThrow(Throwable throwable) {
   throw throwable;
}