CompletionStage 的异常处理
1. 异常处理流程
flowchart TDA[Stage1: 异步任务] -->|正常完成| B[Stage2: 处理结果]A -->|抛出异常| C[Stage2: 捕获异常]C -->|whenComplete| D[记录日志/清理资源\n不改变结果]C -->|handle| E[返回替代结果\n覆盖异常]D -->|继续传递异常| F[后续阶段仍收到异常]E -->|替换为正常结果| G[后续阶段使用新值]
2. 关键方法对比
方法 | 作用 | 是否改变结果 | 示例 |
---|---|---|---|
whenComplete | 记录日志,但保留原结果/异常 | ❌ 不改变 | stage.whenComplete((res, ex) -> log(ex)) |
handle | 处理异常并返回替代结果 | ✔️ 改变 | stage.handle((res, ex) -> ex != null ? 0 : res) |
exceptionally | 仅处理异常,返回默认值 | ✔️ 改变 | stage.exceptionally(ex -> 0) |
3. 分步骤详解(含代码)
场景模拟
假设有一个异步任务可能成功或失败:
CompletableFuture<Integer> stage1 = CompletableFuture.supplyAsync(() -> {if (Math.random() > 0.5) {return 10; // 正常完成} else {throw new RuntimeException("Oops!"); // 故意抛出异常}
});
方式1:未处理异常(直接崩溃)
stage1.thenApply(x -> x * 2) // 若 stage1 异常,此处不执行.thenAccept(System.out::println);
- 流程图:flowchart LRA[Stage1 异常] --> B[thenApply 被跳过]B --> C[thenAccept 被跳过]C --> D[整个链异常结束]
- 结果:整个调用链因异常终止,无输出。
方式2:使用 whenComplete(记录异常,但结果不变)
stage1.whenComplete((result, ex) -> {if (ex != null) {System.out.println("Stage1 异常: " + ex.getMessage());}}).thenApply(x -> x * 2) // 若 stage1 异常,此处接收 null 并抛出 NPE.thenAccept(System.out::println);
- 流程图:flowchart LRA[Stage1 异常] --> B[whenComplete 记录日志]B -->|传递原异常| C[thenApply 接收 null → NPE]C --> D[整个链再次异常]
- 结果:
Stage1 异常: Oops! 抛出 NullPointerException(因为 thenApply 接收 null)
方式3:使用 handle(替换异常为默认值)
stage1.handle((result, ex) -> {if (ex != null) {System.out.println("Stage1 异常,返回 0");return 0; // 替换异常为默认值}return result;}).thenApply(x -> x * 2) // 0 → 0.thenAccept(System.out::println); // 输出 0
- 流程图:flowchart LRA[Stage1 异常] --> B[handle 返回 0]B -->|传递新值| C[thenApply 0 → 0]C --> D[输出 0]
- 结果:
Stage1 异常,返回 0 0
方式4:使用 exceptionally(仅处理异常)
stage1.exceptionally(ex -> {System.out.println("Stage1 异常,返回 -1");return -1;}).thenApply(x -> x * 2) // -1 → -2.thenAccept(System.out::println); // 输出 -2
- 流程图:flowchart LRA[Stage1 异常] --> B[exceptionally 返回 -1]B --> C[thenApply -1 → -2]C --> D[输出 -2]
- 结果:
Stage1 异常,返回 -1 -2
4. 对比总结
场景 | whenComplete | handle | exceptionally |
---|---|---|---|
是否改变结果 | 不改变(只记录) | 可返回新值覆盖异常 | 仅异常时返回默认值 |
后续阶段接收内容 | 原结果或异常 | 新结果或异常 | 新结果或异常 |
典型用途 | 日志记录、资源清理 | 异常恢复 | 简化异常处理 |
5. 最佳实践
-
明确处理边界:
- 在链式调用中尽早处理异常(如
handle
或exceptionally
),避免异常扩散。
stage1.handle(...) // 第一个处理点.thenApply(...).thenAccept(...);
- 在链式调用中尽早处理异常(如
-
区分 whenComplete 和 handle:
- 需要 只记录不修复 时用
whenComplete
。 - 需要 修复异常并继续 时用
handle
。
- 需要 只记录不修复 时用
-
避免 NPE:
- 如果使用
whenComplete
,确保后续阶段能处理可能的null
值。
- 如果使用
最终结论:
CompletionStage 的异常处理像一个「管道中的错误传递」——如果不主动处理,异常会一直传递到链的末端;而通过 handle
/exceptionally
可以像「阀门」一样截断异常,替换为正常值继续流动。