异常
9/6/26JavaJava基础About 4 min
异常体系
Java 把程序运行期间发生的不正常情况(除数为 0、空指针、下标越界等)称为“异常”(Exception)。所有异常类型的根类都是 java.lang.Throwable,它有两个直接子类:Error 和 Exception。
- Error(错误):程序无法处理的严重问题,大多与代码无关,而由 JVM 自身引起,例如
OutOfMemoryError、StackOverflowError、NoClassDefFoundError。这类问题发生时,JVM 一般会选择线程终止,应用程序不应该(也基本无法)捕获它。 - Exception(异常):程序本身可以处理的异常,分为 受检异常(Checked) 和 非受检异常(Unchecked)。
受检异常 vs 非受检异常
| 分类 | 含义 | 编译器是否强制处理 | 典型例子 |
|---|---|---|---|
| 受检异常(Checked) | 编译期必须处理,否则编译不通过 | 是 | IOException、SQLException、ClassNotFoundException |
| 非受检异常(Unchecked) | 运行时才暴露,编译期不强制处理 | 否 | NullPointerException、IndexOutOfBoundsException、IllegalArgumentException |
非受检异常都继承自 RuntimeException,Error 和 RuntimeException 及其子类都被称为非受检(编译器不要求强制捕获)。
异常处理机制
Java 提供 5 个关键字来处理异常:try、catch、finally、throw、throws。
public class ExceptionDemo {
public static void main(String[] args) {
try {
int result = divide(10, 0);
System.out.println(result);
} catch (ArithmeticException e) {
// 捕获指定异常,可以打印日志或做降级处理
System.err.println("除法运算出错:" + e.getMessage());
} catch (Exception e) {
// 可以有多个 catch,子类异常在前、父类在后
e.printStackTrace();
} finally {
// 无论是否发生异常都会执行,常用于释放资源
System.out.println("finally 块始终执行");
}
}
// throws 在方法签名上声明可能抛出的受检异常
public static int divide(int a, int b) throws ArithmeticException {
if (b == 0) {
// throw 主动抛出异常对象
throw new ArithmeticException("除数不能为 0");
}
return a / b;
}
}try-catch-finally 执行顺序
try块中代码正常执行;若发生异常,立即跳转到匹配异常类型的catch。catch可以有多个,自上而下匹配,必须子类在前、父类在后,否则编译报错(父类在前会把子类“吃掉”)。finally块无论是否异常、是否有 return,都会执行,通常用于关闭资源。- 关于
return的一个易错点:finally中的代码在方法返回之前执行。如果finally里有return,它会覆盖try/catch中的return值。
public static int test() {
try {
return 1;
} finally {
// 这里先执行,但不影响上方 return 1 的返回值
System.out.println("finally");
}
}try-with-resources(JDK 7+)
对于实现了 AutoCloseable(或 java.io.Closeable)接口的资源,可以使用 try-with-resources 语法,编译器会自动在 finally 中调用 close(),避免资源泄漏,代码也更简洁。
// JDK 7+:自动关闭资源
try (FileInputStream in = new FileInputStream("a.txt");
FileOutputStream out = new FileOutputStream("b.txt")) {
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}多个资源
try-with-resources 中声明的多个资源用分号隔开,关闭顺序与声明顺序相反(后声明的先关闭)。
自定义异常
通过继承 Exception(受检)或 RuntimeException(非受检)可以定义业务异常。建议提供带 message 和 cause 的构造方法,以便保留原始异常堆栈。
// 业务受检异常
public class BizException extends Exception {
public BizException(String message) {
super(message);
}
public BizException(String message, Throwable cause) {
super(message, cause);
}
}
// 业务运行时异常(更常见,调用方不必强制处理)
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(String orderId) {
super("订单不存在: " + orderId);
}
}常见异常
| 异常类 | 触发场景 |
|---|---|
NullPointerException | 调用 null 对象的方法、访问其属性 |
ArrayIndexOutOfBoundsException | 数组下标越界 |
ClassCastException | 类型强制转换失败 |
IllegalArgumentException | 传入非法/不正确参数 |
ArithmeticException | 算术错误,如除零 |
ConcurrentModificationException | 遍历集合时结构性修改(fail-fast) |
OutOfMemoryError | 堆内存不足 |
StackOverflowError | 递归过深导致栈溢出 |
异常处理最佳实践
- 不要捕获
Throwable/Exception后又“吞掉”(空 catch 块),至少记录日志。 - 受检异常适合“调用方必须处理”的场景;业务异常多用
RuntimeException子类,配合全局异常处理器(如 Spring 的@ControllerAdvice)统一返回。 - 尽量在能处理异常的地方捕获,不要随便向上
throws到main。