Просто оберните проверенное исключение в CompletionException
Еще один момент, который следует учитывать при обработке исключений в CompletableFuture
при использовании completeExceptionally()
, заключается в том, что точное исключение будет доступно в handle()
и whenComplete()
, но оно будет заключено в CompletionException
при вызове join()
или когда оно будет перенаправлено на любой последующий этап.
Таким образом, handle()
или exceptionally()
, примененные к нижестоящему этапу, увидят CompletionException
вместо исходного, и ему придется искать его причину, чтобы найти исходное исключение.
Более того, любой RuntimeException
, созданный любой операцией (включая supplyAsync()
), также заключен в CompletionException
, за исключением случаев, когда это уже CompletionException
.
Учитывая это, лучше перестраховаться и разрешить обработчикам исключений разворачивать CompletionException
s.
Если вы это сделаете, больше нет смысла устанавливать точное (проверенное) исключение для CompletableFuture
и гораздо проще обернуть проверенные исключения в CompletionException
напрямую:
Supplier<Integer> numberSupplier = () -> {
try {
return SupplyNumbers.sendNumbers();
} catch (Exception e) {
throw new CompletionException(e);
}
};
Чтобы сравнить этот подход с подходом Хольгера, я адаптировал ваш код с двумя решениями (simpleWrap()
- это выше, customWrap()
- это код Хольгера. ):
public class TestCompletableFuture {
public static void main(String args[]) {
TestCompletableFuture testF = new TestCompletableFuture();
System.out.println("Simple wrap");
testF.handle(testF.simpleWrap());
System.out.println("Custom wrap");
testF.handle(testF.customWrap());
}
private void handle(CompletableFuture<Integer> future) {
future.whenComplete((x1, y) -> {
System.out.println("Before thenApply(): " + y);
});
future.thenApply(x -> x).whenComplete((x1, y) -> {
System.out.println("After thenApply(): " + y);
});
try {
future.join();
} catch (Exception e) {
System.out.println("Join threw " + e);
}
try {
future.get();
} catch (Exception e) {
System.out.println("Get threw " + e);
}
}
public CompletableFuture<Integer> simpleWrap() {
Supplier<Integer> numberSupplier = () -> {
try {
return SupplyNumbers.sendNumbers();
} catch (Exception e) {
throw new CompletionException(e);
}
};
return CompletableFuture.supplyAsync(numberSupplier);
}
public CompletableFuture<Integer> customWrap() {
CompletableFuture<Integer> f = new CompletableFuture<>();
ForkJoinPool.commonPool().submit(
(Runnable & CompletableFuture.AsynchronousCompletionTask) () -> {
try {
f.complete(SupplyNumbers.sendNumbers());
} catch (Exception ex) {
f.completeExceptionally(ex);
}
});
return f;
}
}
class SupplyNumbers {
public static Integer sendNumbers() throws Exception {
throw new Exception("test"); // just for working sake its not correct.
}
}
Вывод:
Simple wrap
After thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Before thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Join threw java.util.concurrent.CompletionException: java.lang.Exception: test
Get threw java.util.concurrent.ExecutionException: java.lang.Exception: test
Custom wrap
After thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Before thenApply(): java.lang.Exception: test
Join threw java.util.concurrent.CompletionException: java.lang.Exception: test
Get threw java.util.concurrent.ExecutionException: java.lang.Exception: test
Как вы заметите, единственная разница в том, что whenComplete()
видит исходное исключение перед thenApply()
в случае customWrap()
. После thenApply()
и во всех остальных случаях исходное исключение переносится.
Самое удивительное, что get()
развернет CompletionException
в случае Simple wrap и заменит его на ExecutionException
.
person
Didier L
schedule
09.08.2018