计算机程序的思维逻辑 (25):Java 异常处理机制深度解析
接上篇异常基本概念,本节系统讲解 try-catch-finally 匹配规则、重新抛出、finally 陷阱、throws 声明、Checked/Unchecked 选型哲学,以及工程级异常处理逻辑。
catch 匹配机制
- 自上而下找**第一个**匹配的
catch(含子类匹配)
- 子类异常必须写在父类前面,否则被屏蔽
- 利用
e.getMessage()、e.printStackTrace() 定位问题
- 生产环境应输出到日志框架(SLF4J/Log4j2),而非标准流
try { ... }
catch (NumberFormatException e) { log.warn("非法数字", e); }
catch (RuntimeException e) { log.error("运行时异常", e); }
catch (Exception e) { log.error("兜底", e); }
重新抛出
catch (NumberFormatException e) {
log.warn("非法数字", e);
throw new AppException("输入格式错误", e); // 包装异常链
}
catch (Exception e) {
log.error("未知错误", e);
throw e; // 原样抛出
}
- 包装异常:补充上下文、隐藏实现细节、形成异常链(
getCause() 可溯源)
- 原样抛出:当前层无法完全处理,交由上层决策
finally 陷阱全景
| 场景 | 行为 | 结论 |
| 正常返回 | try { return val; } finally { val = 2; } | 返回原 val(编译器用临时变量保存) |
| finally 中 return | finally { return 2; } | **覆盖** try/catch 返回值,**吞掉**异常 |
| finally 抛异常 | finally { throw new RuntimeException(); } | 原异常**丢失**,仅抛出 finally 的异常 |
铁律:finally 仅用于资源释放,**严禁**包含 return 或抛出异常;若调用可能抛异常的代码,必须内部 try-catch 处理。
throws 声明
public void test() throws AppException, SQLException, NumberFormatException { ... }
- 声明方法**可能**抛出的受检异常,强制调用者处理
RuntimeException 及子类**无需**声明
- 父类声明宽泛异常,子类重写只能抛出同类或子类异常
- 调用者处理方式:
catch 吞掉、或继续 throws 向上传递
Checked vs Unchecked 选型争论
| 观点 | 主张 |
| 传统派 | Checked 表不可预期的外部错误(IO/DB),强制处理;Unchecked 表编程 Bug,应修代码而非 catch |
| 现代派 | 服务端程序**不能**因 Bug 退出,所有异常统一用 Unchecked,避免层层 throws 污染签名 |
| 工程共识 | **项目内统一**优于对错;遵循依赖库约定;关键是「在合适层级以合适方式处理」 |
异常处理的工程逻辑
- 自己能处理 → 处理完毕,不再上抛
- 自己处理不了 → 包装上下文信息,重新抛出
- 顶层入口(Controller/主线程) → 统一转换为用户友好提示 / 记录完整堆栈到日志 / 告警
- 第三方调用 → 引入容错:重试、降级、熔断、异步补偿
这与组织汇报逻辑同构:各层级解决力所能及的问题,无法解决的带着上下文上报,最终 CEO 为全局负责。
小结
异常机制将「正常流」与「异常流」分离,避免返回值检查散落业务代码,实现自动向上传播、集中处理。核心价值:**可读性、可靠性、可维护性**三重提升。
- 上一篇文章: Java 多线程入门:进程与线程关系、Thread 继承创建线程、运行特性解析
- 下一篇文章: Spring MVC 學習筆記(一):MVC 架構概覽與環境搭建實戰
Copyright ©
程式碼之家 All Rights Reserved