簡體   English   中英

CompletableFuture#exceptionally 重新拋出已檢查的異常

[英]CompletableFuture#exceptionally rethrow checked exception

假設我想在遇到特定異常時用一些值進行恢復,否則返回失敗的 future 和異常。 我會期待這樣的事情:

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 function 會拋出一個已檢查的異常,我想在鏈式方法中處理它。 我試過return throwablethrow throwable ,但都沒有編譯。 CompletableFuture是否為這種情況提供任何解決方案? 我知道作為exceptionally方法參數的Function接口不會拋出任何異常 - 在這種情況下我只想返回已經失敗的未來。 我想找到使用 Java 8 的解決方案。

正如霍爾格所寫,這通常是不可能的。
但是,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;
}

您還可以使用ExceptionUtils.rethrow() (感謝vlp )將其存檔。

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

在這種情況下,由於上一階段是基於Supplier ,因此無法接收已檢查的異常,該Supplier不允許拋出已檢查的異常。

因此,您可以處理所有未檢查的異常,並為應該不可能發生的可拋出事件引發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()調用者都將獲得除封裝在CompletionException CompletionExceptionCancellationException外的所有異常。 例如,當我使用

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

因此,我可以利用CompletionException來包裝任意的throwable,這是因為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);
    });

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM