throwable是所有异常的根类,但不应被直接捕获或抛出;应关注其子类error(jvm严重错误,不可恢复)和exception(开发者应处理的异常,分运行时异常和受检异常)。

Throwable 是所有异常的根,不是用来 catch 的
Java 里所有能被抛出、捕获的异常,都必须是 Throwable 的子类。但它本身是抽象类,不能直接 new Throwable() 抛出(编译会过,但毫无意义)。真正该关注的是它的两个直接子类:Error 和 Exception。
常见错误现象:
— 在 catch 块里写 catch (Throwable t),以为“兜底最全”,结果把 OutOfMemoryError 这类本该让 JVM 崩溃的错误也吞了
— 把 Throwable 当成通用日志记录入口,忽略它混杂了“程序可恢复”和“系统已崩溃”两类完全不同的信号
-
Error表示严重问题,比如StackOverflowError、NoClassDefFoundError,JVM 自身出问题,应用层不应尝试恢复 -
Exception才是设计给开发者处理的,分RuntimeException(运行时异常,不强制 try-catch)和受检异常(IOException等,编译器强制处理) - 除非你在写监控代理、字节码增强工具等底层设施,否则永远不要 catch
Throwable
RuntimeException 不需要声明,但不代表可以忽略
NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 都是 RuntimeException 的子类,编译器不强制你 try-catch 或 throws,但这不等于它们不重要。
使用场景:
— 参数校验失败用 IllegalArgumentException,比 if + return 更明确
— 调用方传入 null 且逻辑上不允许时,立刻抛 NullPointerException(而不是等后续 NPE 爆在深层调用栈)
— 集合操作越界,优先暴露 IndexOutOfBoundsException,而不是默默返回 null 或默认值
立即学习“Java免费学习笔记(深入)”;
- 别为了“少写 try”而把本该提前暴露的逻辑错误藏成静默行为
- 自定义业务异常建议继承
RuntimeException,比如InsufficientBalanceException,避免污染方法签名 - 注意
Optional不是 RuntimeException 的替代品:它解决的是“可能为空”的建模问题,不是“参数非法”的语义问题
受检异常(Checked Exception)的设计意图常被误读
IOException、SQLException 这些必须显式处理的异常,初衷是提醒你:这个操作有外部依赖失败的可能,且失败后大概率无法自动恢复,需要你决定是重试、降级、还是向上透传。
容易踩的坑:
— catch (IOException e) { e.printStackTrace(); }:日志没进收集系统,错误被吃掉,线上完全不可见
— throws IOException 往上一直抛到 main 或 Servlet 入口,却不做任何响应封装,导致 HTTP 500 返回裸堆栈
— 把 InterruptedException 忽略掉,不清除中断状态,导致线程池任务无法响应 shutdown
- 受检异常不是“麻烦”,而是契约:它告诉你“这里可能失败,你得想好怎么办”
- 不要用
try { ... } catch (Exception e) { throw new RuntimeException(e); }消解检查——这等于把契约撕了还假装没发生 - 现代框架(如 Spring)会把部分受检异常转为非受检(如
SQLException→DataAccessException),这是封装,不是绕过设计意图
自定义异常别只继承 Exception,要选对父类
写 OrderNotFoundException 或 PaymentTimeoutException 时,继承谁决定了它怎么被用。
参数差异:
— 继承 RuntimeException:调用方无需声明,适合业务逻辑中“不该发生但发生了”的情况(如查不到订单 ID 对应的数据)
— 继承 Exception:强制调用方处理,适合“大概率发生且需主动应对”的外部交互(如调第三方支付超时)
— 绝对不要继承 Error:那是给 JVM 留的,你加个业务类进去只会误导排查者
- 构造函数至少提供
String message和Throwable cause两个重载,方便链路追踪 - 如果异常携带上下文数据(如订单号、用户 ID),用字段 + getter 封装,别塞进 message 字符串里解析
- Spring Boot 项目中,配合
@ControllerAdvice统一处理时,异常类型决定了它走哪个@ExceptionHandler,父类选错会导致降级逻辑失效
层级关系本身不复杂,难的是每次抛出、捕获、封装时,是否真在想:这个信号,到底该由谁响应?响应成什么?很多人卡在这一步,不是不会写 extends,而是没想清异常背后的控制流契约。










