写好异常处理的关键是分清拦截、放行和忽略的场景:受检异常需强制处理并恢复,运行时异常应从源头修复,资源用try-with-resources自动释放,日志须含上下文且对用户脱敏友好。

写好异常处理,关键不是堆代码,而是分清哪些该拦、哪些该放、哪些根本不用管。Java里最常混淆的,就是受检异常和运行时异常——它们的设计意图完全不同,处理方式自然也不能一刀切。
受检异常:编译器逼你正视风险
像 IOException、SQLException 这类异常,编译器会强制你处理。这不是繁琐,而是提醒:这些错误来自外部(文件不存在、网络断开、数据库挂了),程序大概率能恢复,也理应提前准备。
- 必须用
try-catch捕获,或在方法签名加throws向上抛出 - 捕获后别只写个空
catch或打个日志就完事,要给出实际应对动作,比如重试、降级、提示用户稍后再试 - 多个受检异常共存时,避免用
catch (Exception e)一锅端;按类型分别处理,比如FileNotFoundException可引导用户检查路径,SocketTimeoutException则适合自动重试
运行时异常:暴露逻辑漏洞,不该靠 catch 来兜底
NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 这些,本质是代码写错了。编译器不强制捕获,是因为它希望你从源头修复,而不是掩盖问题。
- 优先做防御性编程:调用前判空、集合操作前检查 size、参数传入时校验合法性
- 真需要捕获时(比如解析第三方数据时无法保证格式),也要明确知道“为什么这里可能出错”,并做有意义的处理,而不是吞掉异常或打印一句“出错了”
- 自定义业务异常建议继承
RuntimeException,比如InsufficientBalanceException,这样调用方不用被迫写一堆throws,语义也更清晰
资源清理:别让 finally 成摆设
打开文件、数据库连接、网络套接字……这类资源,不管是否发生异常,都得释放。靠 finally 不够稳,现代写法推荐 try-with-resources:
- 只要资源实现了
AutoCloseable接口(绝大多数标准 I/O 类都支持),就能自动关闭 - 比手写
finally更简洁,也杜绝了因异常嵌套导致 close() 本身再抛异常的风险 - 例如:
try (FileReader reader = new FileReader("data.txt")) { ... },括号内资源会在 try 结束后自动关闭
异常信息:别让日志变成谜语
捕获异常后,光 e.printStackTrace() 或 log.error("出错了") 是无效的。排查问题时,真正有用的是上下文。
- 记录关键业务参数(如用户 ID、订单号、请求 URL)
- 用
e.getMessage()+e.getCause()+ 完整堆栈(尤其在 warn/error 级别) - 对用户展示的错误信息,必须脱敏且友好,比如“支付失败,请检查网络后重试”,而不是“NullPointerException at com.xxx.PaymentService.pay(PaymentService.java:42)”











