1. 简介
Java 8 引入了基于 Future
的新抽象——CompletableFuture
类,主要用于执行异步任务。它的出现是为了解决传统 Future
API 的各种痛点。
本文将深入探讨在使用 CompletableFuture
时处理异常的几种方式。
2. CompletableFuture 快速回顾
先简单回顾 CompletableFuture
的核心概念:它是 Future
的实现类,支持执行和链式调用异步操作。异步任务通常有三种完成状态:
- ✅ 正常完成
- ❌ 异常完成
- ⚠️ 被外部取消
CompletableFuture
提供了丰富的 API 方法应对这些场景。与其他方法类似,异常处理方法也提供同步、异步和指定线程池的异步版本。下面我们逐一分析异常处理方案。
3. handle() 方法
handle()
方法允许我们访问并转换整个 CompletionStage
的结果,无论成功还是失败。它接受一个 BiFunction
函数式接口,包含两个参数:
- 前一阶段的结果
- 可能发生的
Exception
⚠️ 关键点:**这两个参数都可能为 null
**(正常完成时异常为 null
,异常完成时结果为 null
)。
示例代码:
@ParameterizedTest
@MethodSource("parametersSource_handle")
void whenCompletableFutureIsScheduled_thenHandleStageIsAlwaysInvoked(int radius, long expected)
throws ExecutionException, InterruptedException {
long actual = CompletableFuture
.supplyAsync(() -> {
if (radius <= 0) {
throw new IllegalArgumentException("Supplied with non-positive radius '%d'");
}
return Math.round(Math.pow(radius, 2) * Math.PI);
})
.handle((result, ex) -> {
if (ex == null) {
return result;
} else {
return -1L;
}
})
.get();
Assertions.assertThat(actual).isEqualTo(expected);
}
static Stream<Arguments> parameterSource_handle() {
return Stream.of(Arguments.of(1, 3), Arguments.of(1, -1));
}
核心特点:handle()
总是会执行,返回新的 CompletionStage
,并通过 get()
获取其转换后的结果。
4. exceptionally() 方法
当只需要处理异常场景时,handle()
显得不够灵活。此时可用 exceptionally()
——仅在前一阶段异常完成时执行回调。若无异常,则跳过回调继续执行后续链。
示例代码:
@ParameterizedTest
@MethodSource("parametersSource_exceptionally")
void whenCompletableFutureIsScheduled_thenExceptionallyExecutedOnlyOnFailure(int a, int b, int c, long expected)
throws ExecutionException, InterruptedException {
long actual = CompletableFuture
.supplyAsync(() -> {
if (a <= 0 || b <= 0 || c <= 0) {
throw new IllegalArgumentException(String.format("Supplied with incorrect edge length [%s]", List.of(a, b, c)));
}
return a * b * c;
})
.exceptionally((ex) -> -1)
.get();
Assertions.assertThat(actual).isEqualTo(expected);
}
static Stream<Arguments> parametersSource_exceptionally() {
return Stream.of(
Arguments.of(1, 5, 5, 25),
Arguments.of(-1, 10, 15, -1)
);
}
⚠️ 注意:若异常已被 handle()
捕获,后续的 exceptionally()
不会执行:
@ParameterizedTest
@MethodSource("parametersSource_exceptionally")
void givenCompletableFutureIsScheduled_whenHandleIsAlreadyPresent_thenExceptionallyIsNotExecuted(int a, int b, int c, long expected)
throws ExecutionException, InterruptedException {
long actual = CompletableFuture
.supplyAsync(() -> {
if (a <= 0 || b <= 0 || c <= 0) {
throw new IllegalArgumentException(String.format("Supplied with incorrect edge length [%s]", List.of(a, b, c)));
}
return a * b * c;
})
.handle((result, throwable) -> {
if (throwable != null) {
return -1;
}
return result;
})
.exceptionally((ex) -> {
System.exit(1); // 这行不会执行
return 0;
})
.get();
Assertions.assertThat(actual).isEqualTo(expected);
}
5. whenComplete() 方法
whenComplete()
接受 BiConsumer
参数(结果和异常),但与前两者有本质区别:它不会转换异常结果。即使回调总是执行,前一阶段的异常仍会继续传播。
示例代码:
@ParameterizedTest
@MethodSource("parametersSource_whenComplete")
void whenCompletableFutureIsScheduled_thenWhenCompletedExecutedAlways(Double a, long expected) {
try {
CountDownLatch countDownLatch = new CountDownLatch(1);
long actual = CompletableFuture
.supplyAsync(() -> {
if (a.isNaN()) {
throw new IllegalArgumentException("Supplied value is NaN");
}
return Math.round(Math.pow(a, 2));
})
.whenComplete((result, exception) -> countDownLatch.countDown())
.get();
Assertions.assertThat(countDownLatch.await(20L, java.util.concurrent.TimeUnit.SECONDS));
Assertions.assertThat(actual).isEqualTo(expected);
} catch (Exception e) {
Assertions.assertThat(e.getClass()).isSameAs(ExecutionException.class);
Assertions.assertThat(e.getCause().getClass()).isSameAs(IllegalArgumentException.class);
}
}
static Stream<Arguments> parametersSource_whenComplete() {
return Stream.of(
Arguments.of(2d, 4),
Arguments.of(Double.NaN, 1)
);
}
关键现象:第二次测试中,whenComplete()
虽执行了,但 IllegalArgumentException
被包装成 ExecutionException
抛出。
6. 未处理异常
未捕获的异常会导致 CompletableFuture
以异常状态完成,但不会直接传播到调用方。前文示例中 get()
抛出 ExecutionException
,是因为我们尝试获取异常完成的结果。
检查 CompletableFuture
状态的方法:
- ✅
isCompletedExceptionally()
/isCancelled()
/isDone()
(布尔值) - ✅
state()
(返回State
枚举,如RUNNING
/SUCCESS
)
建议在调用 get()
前检查状态,避免踩坑。
7. 总结
本文系统分析了 CompletableFuture
中处理异常的三种核心方法:
handle()
:万能型,总是执行并转换结果exceptionally()
:异常专用,简洁高效whenComplete()
:仅做副作用,不处理异常传播
完整源码见 GitHub 仓库