问题
The CompletionStage Javadoc states:
[...] if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause.
Seeing as exceptional completions always wrap exceptions in CompletionException
why do exceptionally()
, whenComplete()
and handle()
represent the exception as Throwable
instead of CompletionException
?
This matters because it prevents one from directly re-throwing exceptions inside these methods.
Is it possible for these methods to receive an exception other than CompletionException
? Or can I safely force a cast to this type?
(I ran some tests locally, as well as dig through the CompletableFuture source-code and, at first glance, I do not see how any other type of exception could be thrown.)
回答1:
Is it possible for these methods to receive an exception other than
CompletionException
?
Yes, it is possible and you shouldn't cast to CompletionException
without an instanceof
check (or a review of your usage).
Take this example
CompletableFuture<Void> root = new CompletableFuture<>();
root.whenComplete((v, t) -> {
System.out.println(t.getClass()); // class java.io.IOException
});
root.completeExceptionally(new IOException("blow it up"));
whenComplete
will receive the IOException
rather than a CompletionException
wrapping it. The same behavior applies to exceptionally
and handle
.
A stage's computation is defined in the Javadoc:
The computation performed by a stage may be expressed as a
Function
,Consumer
, orRunnable
(using methods with names including apply, accept, or run, respectively) depending on whether it requires arguments and/or produces results.
I believe this quote
if a stage's computation terminates abruptly with an (unchecked) exception or error
is referring to one of those Function#apply
, Consumer#accept
, or Runnable#run
methods terminating abruptly because of a thrown exception, not because a stage completed exceptionally through some other mechanism.
Note also that the Javadoc says
This interface does not define methods for initially creating, forcibly completing normally or exceptionally, probing completion status or results, or awaiting completion of a stage. Implementations of
CompletionStage
may provide means of achieving such effects, as appropriate
In other words, the interface allows implementations to complete stages exceptionally without abruptly terminating any computation. I think this allows for new behavior.
If we extend my example from before
CompletableFuture<Void> root = new CompletableFuture<>();
CompletableFuture<Void> child = root.whenComplete((v, t) -> {
System.out.println(t.getClass()); // class java.io.Exception
});
child.whenComplete((v, t) -> {
System.out.println(t.getClass()); // class java.util.concurrent.CompletionException
});
root.completeExceptionally(new IOException("blow it up"));
You'll notice the completion attached to the child
receives a CompletionException
wrapping the original IOException
. This isn't obvious to me from the Javadoc, which states
Returns a new
CompletionStage
with the same result or exception as this stage
All in all, it seems like the raw exception from a completeExceptionally
is passed down to direct dependents, while dependents of dependents receive an enclosing CompletionException
.
来源:https://stackoverflow.com/questions/49230980/does-completionstage-always-wrap-exceptions-in-completionexception