使用CompletableFuture调整异常的正确方法

时间:2017-06-16 00:02:49

标签: java exception-handling java-8 completable-future

我正在努力将CompletableFuture链接起来以适应异常。虽然我有一些有用的东西,但我不明白它为什么会起作用。

@Test
public void futureExceptionAdapt() throws ExecutionException, InterruptedException {
    class SillyException extends Exception { }
    class AdaptedException extends Exception { AdaptedException(SillyException silly) { } }

    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        sleepForThreeSeconds();
        if (true)
            throw new CompletionException(new SillyException());
        return 5;
    })

    .thenApplyAsync(val -> val * 10);

    CompletableFuture<Integer> futureAdaptException = future.exceptionally((t) -> {
        if (t instanceof CompletionException && t.getCause() instanceof SillyException) {
            System.out.println("adapt SillyException to AdaptedException");
            SillyException silly = (SillyException) t.getCause();
            future.obtrudeException(new AdaptedException(silly));
        }
        return null;
    });

    try {
        future.get();
        fail("future should have failed with an exception");
    } catch (ExecutionException e) {
        assertTrue("got exception: " + getCauseClass(e),
                   e.getCause() instanceof AdaptedException);
    }

    // I am not sure why the above succeeds
    // because I did not call futureAdaptException.get()
    // According to the IDE futureAdaptException is an unused variable at this point

    assertTrue("expect futureAdaptException to have failed with AdaptedException but instead the result is: " + futureAdaptException.get(),
               futureAdaptException.isCompletedExceptionally());
}

private static void sleepForThreeSeconds() {
    try {
        Thread.sleep(3000L);
    } catch (InterruptedException e) {
    }
}

private static String getCauseClass(Throwable t) {
    if (t.getCause() == null) return "null";
    return t.getCause().getClass().getName();
}

第一个问题是,当我致电futureAdaptException时,为什么会调用future.get()

其次,有没有办法让futureAdaptException.get()失败并带有所需的异常?因为我不想创建虚拟对象,因为我不需要。您无法修改lambda中的futureAdaptException

或者可能有更好的方法来调整异常。或者可能有exceptionally()的变体将对象保持在异常阶段(似乎return null导致futureAdaptException将对象置于具有值null的正常阶段。或许我们不应该在CompletableFuture s中调整异常。

    @Test
public void futureStrangeBehaviorFromChaining1() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        System.out.println("sleep for 3 seconds then return 5");
        sleepForThreeSeconds();
        return 5;
    });

    future.thenApplyAsync(val -> {
        System.out.println("multiply by 3");
        return val*3;
    });

    sleepForFiveSeconds();

    assertEquals(5, future.get().intValue());

    sleepForFiveSeconds();

    assertEquals(5, future.get().intValue());
}

@Test
public void futureStrangeBehaviorFromChaining2() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        System.out.println("sleep for 3 seconds then return 5");
        sleepForThreeSeconds();
        return 5;
    })

    .thenApplyAsync(val -> {
        System.out.println("multiply by 3");
        return val*3;
    });

    sleepForFiveSeconds();

    assertEquals(15, future.get().intValue());

    sleepForFiveSeconds();

    assertEquals(15, future.get().intValue());
}

1 个答案:

答案 0 :(得分:1)

致电futureAdaptException时,

future.get()并非“调用”。发生的情况是您使用future.exceptionally()创建了它,因此当future异常完成时会自动触发。

即使futureAdaptException未使用(因此您可以删除变量),exceptionally()仍有副作用。

您从CompletableFuture获得的exceptionally()将成功或失败,具体取决于您在传入函数中执行的操作。如果您希望它失败,您仍然可以再次抛出异常:

CompletableFuture<Integer> futureAdaptException = future.exceptionally((t) -> {
    if (t instanceof CompletionException && t.getCause() instanceof SillyException) {
        System.out.println("adapt SillyException to AdaptedException");
        SillyException silly = (SillyException) t.getCause();
        final AdaptedException ex = new AdaptedException(silly);
        future.obtrudeException(ex);
        throw new CompletionException(ex);
    }
    return null;
});

请注意,您应该避免使用obtrudeException(),因为这是不确定的。事实上,我很惊讶你的第一个断言成功¹。 CompletableFuture返回的exceptionally()如果成功则会与原始结果相同,因此您应该使用该结果。

¹我肯定认为这是由于JDK中的一个错误。如果您在sleepForThreeSeconds()中添加exceptionally(),则测试仍会通过。但是,如果在future.get()之前添加睡眠时间超过3秒,则断言失败并且您将获得原始异常。如果您在完成之前致电get(),它似乎也会等待exceptionally()执行。我已发布this question以更好地理解这一点。