在Java里如何使用try catch优化程序流程_Java异常控制技巧说明

应避免用try-catch做流程控制,因其性能差且掩盖问

题;推荐使用校验方法、Optional、try-with-resources及具体异常捕获,并合理设计自定义异常与事务一致性。

不要用 try-catch 做流程控制

Java 中最常被误用的异常机制,就是把 try-catch 当成 if-else 用。比如用 NumberFormatException 捕获来判断字符串是否为数字,或靠 NullPointerException 触发来跳过空值处理——这不仅慢,还掩盖真实问题。

JVM 抛异常是重量级操作:要填充栈轨迹、实例化异常对象、触发异常表查找。一次 catch 的开销通常是普通分支的 10–100 倍。

  • ✅ 正确做法:用 String.matches("\\d+")Integer.parseInt() 前先校验格式
  • ✅ 空值检查优先用 Objects.nonNull(obj) 或 Optional 链式调用
  • ❌ 错误示范:
    try { Integer.valueOf(str); return true; } catch (NumberFormatException e) { return false; }

try-with-resources 是唯一推荐的“语法糖式”异常处理

当资源(InputStreamConnectionFileChannel 等)必须显式关闭时,try-with-resources 不仅简洁,还能保证 close() 在异常发生时仍被执行,且自动抑制多重异常(suppressed exceptions)。

  • 资源类必须实现 AutoCloseable 接口(JDK 7+ 所有标准 I/O 类都满足)
  • 多个资源用分号隔开,关闭顺序与声明顺序相反
  • try 块抛异常,且 close() 也抛异常,后者会被压制,可通过 Throwable.getSuppressed() 查看
try (FileInputStream fis = new FileInputStream("a.txt");
     BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
    String line = reader.readLine();
} catch (IOException e) {
    // 这里捕获的是 try 块主逻辑或 close() 中首个未被压制的异常
}

捕获具体异常类型,避免裸 catch (Exception e)

泛化捕获会吞掉本该由上层处理的异常(如 InterruptedException),或干扰监控系统对错误类型的统计。JVM 不会阻止你写 catch (Throwable t),但生产代码中几乎从不合法。

  • 优先捕获最具体的子类:比如 SQLException 而非 ExceptionIllegalArgumentException 而非 RuntimeException
  • 对可恢复异常(如网络超时)做重试或降级;对不可恢复异常(如 NoClassDefFoundError)应快速失败并记录堆栈
  • 日志中必须打印完整异常:用 log.error("msg", e),而不是 log.error("msg: " + e.getMessage())

自定义异常要区分「检查型」和「非检查型」

是否继承 Exception 还是 RuntimeException,本质是在回答:“调用方是否**必须**处理它?”

  • ✅ 检查型异常(extends Exception):业务中明确需要强制处理的场景,例如支付接口返回「余额不足」,调用方必须弹窗提示用户充值
  • ✅ 非检查型异常(extends RuntimeException):程序逻辑错误或外部依赖故障,如参数校验失败、远程服务不可达,通常由全局异常处理器统一响应 HTTP 500
  • ⚠️ 注意:自定义异常类名需以 Exception 结尾(如 InsufficientBalanceException),且建议提供带 cause 的构造函数
实际项目里最容易被忽略的,是异常发生后上下文状态是否一致——比如事务已提交一半、缓存已更新但 DB 回滚了。这类问题不会在 try-catch 语法里暴露,得靠设计时就明确每个异常分支的副作用边界。