
写了几年Java异常处理这块的代码依然是最容易藏污纳垢的地方。空catch块、吞掉堆栈、在finally里return——这些不是新手专利很多写了三五年的人照样在犯。异常机制的设计初衷是好的让错误处理与业务逻辑分离。但好工具用错了姿势比不用更糟糕。本文直击最常见的几个误区给出真正能落地的正确姿势。误区一异常抓了就吞等于没抓java复制下载try { // 业务代码 } catch (Exception e) { // 什么都不写 }这是Java世界里最令人发指的罪行。空catch块等于告诉系统出错了但我不在乎你闭嘴。线上出了NullPointerException日志里干干净净排查时只能靠猜。更离谱的是有人写e.printStackTrace()就以为交了差——在生产环境这个输出可能落在stdout里被日志系统忽略或者被大量堆栈刷屏导致性能问题。正确姿势要么记录日志要么重新抛出要么两者都做。用SLF4J记录完整的堆栈信息java复制下载try { // 业务代码 } catch (IOException e) { log.error(文件读取失败文件路径: {}, filePath, e); throw new BusinessException(文件处理异常, e); }关键是保留原始异常链不要只抛出异常消息而丢失了cause。误区二在finally块中return或抛出异常java复制下载try { return 1; } finally { return 2; // 最终返回2try里的return被覆盖 }这是Java语法允许的但语义上极其危险。finally块的return会覆盖try或catch里的所有返回值导致异常丢失或返回值与预期不符。如果finally块自身抛出异常那try块里的异常也会被吞掉调用方看到的是一个全新的异常。正确姿势finally块只做资源清理不要在里面写任何改变控制流的逻辑return、break、throw。资源释放方法本身要单独处理异常java复制下载finally { if (conn ! null) { try { conn.close(); } catch (SQLException e) { log.error(关闭连接失败, e); // 绝不在这里throw或return } } }误区三捕获过于宽泛的异常类型java复制下载try { // 复杂业务 } catch (Exception e) { log.error(系统异常, e); }一把抓什么都往Exception里塞。这样做的问题有三一是吞掉了不同异常类型的语义差异NullPointerException和SQLException的处理方式完全不同二是可能捕获了不该捕获的RuntimeException掩盖了代码逻辑缺陷三是调用方无法针对特定异常做差异化处理。正确姿势从具体到宽泛分别捕获。能预见的检查型异常单独处理运行时异常让它们自然往上抛由上层统一处理java复制下载try { // 业务代码 } catch (FileNotFoundException e) { log.error(配置文件缺失, e); throw new ConfigException(e); } catch (IOException e) { log.error(IO读写异常, e); throw new SystemException(e); }误区四用异常控制业务流程java复制下载try { user findUser(id); } catch (UserNotFoundException e) { // 用户不存在走注册流程 }异常的本质是非预期情况。把正常的分支逻辑用异常来实现性能开销大异常对象的生成包含完整的堆栈填充而且代码可读性极差。正确姿势用返回值或Optional表达业务状态java复制下载OptionalUser userOpt findUser(id); if (userOpt.isPresent()) { // 登录流程 } else { // 注册流程 }只有真正不可恢复的、系统级的错误才适合抛异常例如数据库连接失败、配置加载错误。误区五受检异常滥用成灾Java发明了受检异常Checked Exception本意是强迫开发者处理可恢复的错误。但现实中被滥用得面目全非——接口里每个方法都throws Exception调用方要么接着往上抛要么空catch最终受检异常流于形式。正确姿势区分场景。对调用方可以合理恢复的场景如网络超时后重试用受检异常。对调用方无能为力的场景如参数非法、空指针用非受检异常RuntimeException。Spring、Hibernate等主流框架的实践已经证明了绝大多数场景下非受检异常更清爽。如果你的项目中受检异常占了异常体系的大头很可能是过度设计了。误区六异常日志缺少上下文java复制下载catch (SQLException e) { log.error(数据库异常, e); }日志一打只看到异常堆栈却不知道是哪个表、哪个字段、哪个操作出了问题。线上排查时你只能凭时间戳和运气去猜。正确姿势在日志中嵌入业务上下文——用户ID、订单号、请求参数等关键信息java复制下载catch (SQLException e) { log.error(更新用户订单失败, userId: {}, orderId: {}, userId, orderId, e); }写在最后异常处理的本质是边界管理——你在你和调用方之间划出一道清晰的界线哪些情况我处理哪些情况我抛给你。处理得当的异常是系统最后的尊严处理不当的异常是留给运维和下游的噩梦。好的异常代码不是让程序不出错而是出错时你能最快找到并修复它。记住这句话比背一百个异常类的继承关系都管用。