我正在努力将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());
}
答案 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以更好地理解这一点。