Java异常机制是责任分配系统,强制分类错误、传播路径和恢复策略;Throwable为根,Error表严重JVM问题不可捕获,Exception表可干预错误,分已检查(如IOException)与未检查(如NullPointerException);try-with-resources防资源泄漏;throw抛实例,throws声明可能异常;自定义异常需明确checked/unchecked并脱敏敏感信息。

Java异常机制不是“要不要捕获”的选择题,而是“谁该在什么时机、以什么方式响应错误”的责任分配系统。它强制把错误分类、传播路径和恢复策略写进类型签名和控制流里。
Throwable 是所有异常的根,但 Error 和 Exception 的分工必须分清
Java 要求所有异常对象都继承 Throwable,但它只允许两类子类参与正常流程:Error 和 Exception。两者的语义边界非常明确:
-
Error表示 JVM 无法继续运行的严重问题,比如OutOfMemoryError、StackOverflowError—— 这些不能也不该被业务代码 try-catch -
Exception才是程序预期中可能出错、且调用方有能力干预的场景,比如IOException、SQLException - 进一步区分:
RuntimeException及其子类(如NullPointerException、ArrayIndexOutOfBoundsException)是“未检查异常”,编译器不强制处理;其余Exception子类(如IOException)是“已检查异常”,方法签名必须声明throws或用try-catch包裹
try-catch-finally 的执行顺序和资源泄漏风险
很多人以为 finally 总是最后执行,其实它会在 return 语句的值确定后、真正返回前插入执行。更关键的是:如果 try 或 catch 块里有 return,finally 仍会运行,甚至能修改返回值(对引用类型)或覆盖基本类型返回值(需谨慎)。
常见陷阱是手动 close 资源时忘记嵌套 try:
立即学习“Java免费学习笔记(深入)”;
try {
InputStream is = new FileInputStream("a.txt");
// do something
is.close(); // 如果这里抛异常,close 就没执行
} catch (IOException e) {
// handle
}
正确做法是用 try-with-resources(JDK 7+),它自动调用 AutoCloseable.close():
try (InputStream is = new FileInputStream("a.txt")) {
// do something
} catch (IOException e) {
// handle
} // is.close() 在这里隐式调用,无论是否异常
throw 和 throws 的职责完全不同,混用会导致编译失败或逻辑断裂
throw 是动词,用于抛出一个具体的异常实例;throws 是修饰符,写在方法声明上,告诉调用方“我可能向上扔这些异常”。两者不能替代:
- 如果方法体内用了
throw new IOException(),但声明里没写throws IOException,编译直接报错(对已检查异常) - 如果只写了
throws IOException却没在方法体里throw,编译通过,但这是“谎报军情”,误导调用方做无谓的异常处理 - 多个异常用逗号分隔:
throws IOException, SQLException;子类异常可以被父类catch,但throws列表不能省略子类(除非父类已声明)
自定义异常别只继承 Exception,得想清楚它是 checked 还是 unchecked
继承 Exception 得到已检查异常,继承 RuntimeException 得到未检查异常。选错会立刻影响 API 使用体验:
- 业务校验失败(如余额不足、参数非法)适合用未检查异常 —— 调用方不该被强制包裹,而应通过文档或断言提前规避
- 外部依赖失败(如远程服务超时、配置文件缺失)适合用已检查异常 —— 强制调用方决策:重试?降级?还是向上传播?
- 所有自定义异常都应提供带
String message和Throwable cause的构造函数,方便链式异常追踪
最常被忽略的一点:异常消息里不要拼接敏感数据(如密码、token),堆栈里也别打印完整请求体 —— 生产环境日志脱敏不是可选项。










