955 字
约 3 分钟
6
S04-B07 · 异常处理:分类、最佳实践与常见反模式
适用读者:写
catch (Exception e) {}的人。
前置:S04-B05。
实测环境:OpenJDK 21.0.12.1(2026-11-05)。
覆盖旧文:A-1711(173) A-1713(168) A-2603(353) A-1712(117) A-1715(69) = 880 点。
覆盖扩展:E-S04-008(受检 vs 非受检的取舍)。
上一篇:S04-B06 · 接口、抽象类、Lambda 与枚举
下一篇:S04-B08 · 字符串与常量池。
1. 一句话结论
异常分两类:受检(编译期强制处理,表示"可预期的外部失败")与非受检(运行期,表示"程序 bug")。处理原则只有一条:能恢复就处理,不能恢复就抛出并带上上下文。
2. 实测:三个最容易搞错的细节
static int finallyReturn() { try { return 1; } finally { return 2; } }
实测输出:
finally 里的 return 覆盖: 2
使用资源
关闭资源
捕获 NPE: Cannot invoke "String.length()" because "<local0>" is null
外层捕获: 内层
| 现象 | 结论 |
|---|---|
finally 里的 return |
会覆盖 try 里的返回值(且吞掉异常)→ 强烈不推荐 |
| try-with-resources | 自动调用 close(),顺序:资源关闭在离开块时 |
| NPE 消息 | JDK 14+ 的"有帮助的 NPE"会指出具体调用 |
3. 异常分类(E-S04-008)
| 类型 | 例子 | 是否强制处理 | 语义 |
|---|---|---|---|
Error |
OutOfMemoryError、StackOverflowError |
❌ | 不该捕获(JVM 级问题) |
受检 Exception |
IOException、SQLException |
✅ | 可预期的外部失败 |
非受检 RuntimeException |
NullPointerException、IllegalArgumentException |
❌ | 编程错误 |
判据(受检 or 非受检):
| 调用方能否合理地恢复? | 用哪种 |
|---|---|
| 能(重试、换路径、提示用户) | 受检异常 |
| 不能(参数非法、状态错乱) | 非受检 |
4. 五个反模式
| 反模式 | 问题 | 正确做法 |
|---|---|---|
catch (Exception e) {} |
吞异常,问题消失无踪 | 至少记录日志并保留原异常 |
catch 后 printStackTrace() |
生产环境看不到 | 用日志框架 + 上下文 |
throw new RuntimeException(e.getMessage()) |
丢失原始栈 | new RuntimeException("上下文", e) |
| 用异常做流程控制 | 性能与可读性都差 | 用返回值/Optional |
finally 里 return/throw |
覆盖返回值、吞异常 | 只在 finally 里释放资源 |
"丢失原始栈"这一条最致命:e.getMessage() 只有一句话,没有堆栈,线上无法定位。
5. try-with-resources(A-1715)
try (InputStream in = open(); OutputStream out = create()) {
// 使用
} // 自动 close,顺序与声明相反
| 对比 | 传统 try-finally | try-with-resources |
|---|---|---|
| 代码量 | 多 | 少 |
| 异常抑制 | 手动处理 | 自动 addSuppressed |
| 漏关风险 | 高 | 低 |
判据:任何实现了 AutoCloseable 的资源,都用 try-with-resources。
6. 自定义异常的规范
| 要点 | 说明 |
|---|---|
| 继承合适的父类 | 受检失败 → Exception;编程错误 → RuntimeException |
| 带上下文 | 至少 message + 关键 id |
| 保留 cause | 构造器传 Throwable cause |
| 不要过度细分 | 每个场景一个异常类会导致 catch 泛滥 |
7. 自检
finally里return会怎样?为什么不推荐?- 受检与非受检的选择判据是什么?
throw new RuntimeException(e.getMessage())丢了什么?- try-with-resources 相比 try-finally 的两个优势?
- 什么时候该自定义异常?
8. 下一篇
S04-B08 · 字符串、常量池与编码
异常讲完,基础层最后一篇:字符串为什么不可变、常量池与 intern、以及"拼接 20000 次慢 189 倍"的实测。
本篇属于系列《S04 · Java 语言核心》。旧文来源:A-1711/1713/2603/1712/1715;扩展点 E-S04-008。输出为 OpenJDK 21 实测。
S04-B07 · 异常处理:分类、最佳实践与常见反模式
https://www.clxhxhhr.top/posts/5222/ 评论
0 条
还没有评论,先写一条吧。