Throwable顶级类与继承结构
Throwable的定位与职责
Throwable 是 Java 异常体系的根类,它将“异常”和“错误”统一在一个层级之下,提供统一的处理入口。任何 Java 运行时抛出的非正常情况,最终都以 Throwable 或其子类的形式出现。了解它的职责,有助于正确区分致命错误和可恢复的异常。
核心能力来自于父类方法,如 getMessage、getCause、printStackTrace,以及 addSuppressed,用于管理在多异常场景中的信息传递与调试辅助。掌握这些方法,能帮助后端服务快速定位问题根源。
直接子类与层级结构
Throwable 的直接子类分为两大类:Error 和 Exception。其中 Error 通常表示虚拟机自身的问题或资源耗尽,属于不可恢复的情况;Exception 则是应用层可处理的异常族,包括可检的 Checked 异常与非检查的 Unchecked 异常。
Exception 又分为两条路径:受检异常(Checked)需要显式捕获或在方法签名中声明抛出;非受检异常(Unchecked)通常来自 RuntimeException 及其子类,通常是编程错误或不可预测的运行时条件。
// Throwable 的继承关系简化示意
// Throwable
// ├── Error
// │ └── (如 OutOfMemoryError, StackOverflowError)
// └── Exception
// ├── CheckedException (需显式处理)
// │ └── IOException、SQLException 等
// └── RuntimeException (非受检)
// └── NullPointerException、IllegalArgumentException 等
Errors、Exceptions和RuntimeException的区别
Errors与Exceptions的本质区别
Errors 通常代表 JVM 自身的错误或资源耗尽造成的不可恢复情况,例如 OutOfMemoryError 或 StackOverflowError。此类错误往往难以从应用层面恢复,更多是需要通过底层优化和资源治理来避免。
Exceptions 则表示应用层可捕获、可处理的异常,是程序在遇到异常条件时的正常分支之一。通过捕获并处理,应用仍能继续运行或做降级处理。
RuntimeException与受检异常的关系
RuntimeException 是 Exception 的子类,也是所有非受检异常的根,这类异常多源于编程错误,如空指针访问、数组越界等,通常在代码中通过改正逻辑来避免。
受检异常(Checked)必须在方法签名中声明抛出,或在调用处强制捕获,以便调用方显式处理失败场景。合适地使用受检异常可以提升接口契约的可预见性,但过多层级会使调用链冗长。
// 典型场景:受检异常需要被捕获或抛出
public void readConfig() throws IOException {
// 可能抛出 IOException 的 I/O 操作
String s = new String(Files.readAllBytes(Paths.get("config.properties")));
}
<2>(注意:下面的段落中将继续展开后续章节,不进行结论性总结)2>
受检异常(Checked)与非受检异常(Unchecked)
定义与使用场景
受检异常定义清晰、契约性强,适用于可恢复的业务场景,例如网络超时、数据库连接失败等。调用者需要对这些情况做处理或传递上层。
非受检异常多用于编程错误或不可预测的运行时条件,如空指针、越界等,通常通过代码修复和参数校验避免。
如何在后端应用中选择使用
在服务端接口设计中,合理选择是否使用受检异常有助于调用方正确处理边界情况。但过多的受检异常会导致调用链臃肿,因此在业务边界上应结合语义与实现复杂性进行权衡。
对外暴露的 API 尽量保持简洁,将复杂的异常处理封装在内部组件,外部只提供明确的错误信息或自定义业务异常。
// 受检 vs 非受检示例
public class PaymentService {
public void processPayment(PaymentInfo info) throws PaymentProcessingException {
// 可能抛出受检异常,调用方必须处理
if (info == null) throw new IllegalArgumentException("info is null");
// 可能抛出受检异常的场景
// 例如网络故障、数据库事务失败等
// ...
}
}
public class PaymentProcessingException extends Exception {
public PaymentProcessingException(String msg, Throwable cause) {
super(msg, cause);
}
}
自定义异常设计与示例
设计原则与命名
自定义异常应具备清晰的业务含义,命名应反映业务语义,例如 BusinessException、ValidationException。层级设计要避免滥用层级深度,优先在边界上将异常映射为具体的业务异常。
选择继承自 Exception 还是 RuntimeException,前者用于受检异常,后者用于非受检异常,需结合调用方的错误处理策略。
代码示例:创建一个自定义异常
public class BusinessException extends RuntimeException {
private final String errorCode;
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
// 使用示例
public class OrderService {
public void placeOrder(Order order) {
if (order == null) {
throw new BusinessException("ORD-001", "订单对象为空");
}
// 业务逻辑...
}
}
异常传播、捕获与资源管理
传播与抛出策略
异常的传播链决定了调用方需要关注的错误边界,如果方法签名中包含抛出受检异常,调用方必须处理或再次抛出。对于内部实现细节不希望暴露的场景,可以将底层异常包装成自定义业务异常后对外抛出。
适度使用再抛出可以附带更多上下文信息,但要避免丢失原始错误栈。
捕获、再抛出与抑制异常
try-catch 块是最基本的错误处理手段,在需要降级或记录后再抛出时,需保留原始异常信息。对于 try-with-resources,释放资源时若发生异常会产生抑制异常,应通过 getSuppressed 捕获并分析。
public void process(List data) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
// 处理逻辑
String line = br.readLine();
// 可能抛出 IOException
} catch (IOException e) {
// 记录日志或包装后重新抛出
throw new RuntimeException("处理数据失败", e);
}
}
// try-with-resources 的抑制异常示意
public void complexOperation() throws Exception {
class Resource implements AutoCloseable {
@Override public void close() throws Exception {
// 关闭资源时可能抛出异常
throw new Exception("close failed");
}
}
try (Resource r1 = new Resource(); Resource r2 = new Resource()) {
throw new Exception("primary failure");
} catch (Exception e) {
// e.getSuppressed() 记录了 close() 抛出的子异常
for (Throwable t : e.getSuppressed()) {
t.printStackTrace();
}
throw e;
}
}
调试要点与栈信息
关键方法解读
getMessage、getCause、printStackTrace 是定位问题的第一把钥匙。getCause 可以级联上层异常,帮助还原异常的传递链。
StackTraceElement 的顺序 通常首个元素指向异常所在的方法和代码位置,后续的元素用于追溯调用链。通过逐层查看调用栈,可以快速定位到底是哪一段代码触发了异常。
栈信息解读与定位要点
在生产环境中,日志应包含完整栈信息,但为避免性能和日志体积过大,应在必要时才记录全栈信息,同时在错误级别控制输出。
聚焦核心来源,优先定位最靠近“异常根源”的栈帧,如显示“at com.example.service.OrderService.placeOrder(OrderService.java:42)”的条目,通常能快速指向问题代码。
try {
// 可能抛出空指针异常
Object o = null;
o.toString();
} catch (NullPointerException e) {
e.printStackTrace(); // 输出完整栈信息,便于定位
}


