Java 异常处理
异常处理用于描述、传递和处理程序执行期间出现的异常情况。合理使用异常可以帮助定位错误,并在可恢复的场景下采取补救措施。
异常机制不能替代正常的业务判断,也不能保证程序在任何情况下都继续运行。
一、异常体系

Java 中所有可以被抛出的对象都继承自 Throwable。它主要分为 Error 和 Exception 两大类。
Error
Error 通常表示 JVM 或运行环境中的严重问题,例如 OutOfMemoryError。这类问题往往不是普通业务代码能够恢复的,因此一般不在业务代码中捕获后继续执行。
Exception
Exception 表示程序可能处理的异常情况,可以继续分为运行时异常和受检查异常。
运行时异常
RuntimeException 及其子类属于运行时异常。编译器不强制要求捕获或声明,常见类型包括:
NullPointerExceptionArrayIndexOutOfBoundsExceptionClassCastExceptionArithmeticExceptionIllegalArgumentException
运行时异常通常与参数不合法、对象状态错误或程序逻辑缺陷有关,应优先通过校验和修正代码来避免,而不是依赖大量 catch 隐藏问题。
受检查异常
除 RuntimeException 及其子类之外的 Exception 通常属于受检查异常,例如 IOException。
编译器要求程序采用以下方式之一:
- 使用
try-catch捕获。 - 使用
throws声明并继续向调用者传播。
“受检查”发生在编译阶段,但异常对象是在程序运行到相关语句时才可能产生。
二、使用 try-catch-finally
基本语法
try {
// 可能出现异常的代码
} catch (SpecificException e) {
// 处理特定异常
} catch (Exception e) {
// 处理其他异常
} finally {
// 通常用于释放资源或执行收尾操作
}执行规则如下:
-
try中没有抛出异常时,不会执行对应的catch。 -
try中抛出异常时,JVM 会从上到下寻找第一个类型匹配的catch。 -
较具体的子类异常必须写在较宽泛的父类异常之前,否则后面的分支无法到达。
-
finally通常都会执行,但 JVM 被强制终止、进程崩溃或执行System.exit()等特殊情况除外。
示例
import java.util.InputMismatchException;
import java.util.Scanner;
public class ExceptionDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
int i = scanner.nextInt();
int j = scanner.nextInt();
int result = i / j;
System.out.println("相除结果:" + result);
int[] arr = new int[5];
System.out.println(arr[i]);
System.out.println("计算完毕");
} catch (ArithmeticException e) {
System.out.println("除数不能为零");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组索引越界");
} catch (InputMismatchException e) {
System.out.println("请输入整数");
} catch (Exception e) {
System.out.println("程序出现其他异常");
e.printStackTrace();
} finally {
scanner.close();
System.out.println("输入资源已关闭");
}
System.out.println("程序继续执行");
}
}在实际项目中,不应只输出模糊提示后忽略异常。通常需要记录异常栈、返回明确错误信息,或将异常转换为当前业务层能够理解的类型。
三、使用 try-with-resources
try-with-resources 从 Java 7 开始提供,用于自动关闭资源。资源对象必须实现 AutoCloseable 接口。
传统写法
import java.io.FileInputStream;
import java.io.IOException;
public class ResourceDemo {
public static void main(String[] args) {
FileInputStream input = null;
try {
input = new FileInputStream("d:/a.txt");
System.out.println(input.read());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}关闭前必须进行空值判断,否则创建资源失败时,finally 中可能再次出现 NullPointerException。
推荐写法
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileDemo {
public static void main(String[] args) {
try (
FileInputStream input = new FileInputStream("d:/a.txt");
FileOutputStream output = new FileOutputStream("d:/b.txt")
) {
input.transferTo(output);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("程序继续执行");
}
}注意事项:
try圆括号中的资源必须实现AutoCloseable。- 声明多个资源时,关闭顺序与声明顺序相反。
- 资源变量不能在
try块中重新赋值。 - 从 Java 9 开始,也可以使用在外部声明且满足“实际不可变”条件的资源变量。
- 如果业务代码和
close()同时抛出异常,关闭资源时产生的异常会作为受抑制异常保存,可通过getSuppressed()获取。
四、使用 throws 声明异常
throws 写在方法或构造器声明之后,表示当前方法不在这里捕获该异常,而是允许异常继续向调用者传播。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class FileService {
public FileInputStream openFile(String path) throws FileNotFoundException {
return new FileInputStream(path);
}
}throws 本身并没有处理异常。调用者仍然需要捕获该异常,或者继续使用 throws 向上声明。
方法可以声明多个异常:
public void execute() throws FirstException, SecondException {
// 方法体
}
若声明了父类异常,通常不必再重复声明它的子类异常。
五、使用 throw 抛出异常
throw 用于在方法体中抛出一个具体的异常对象。
public void copy(String sourcePath, String targetPath) {
if (sourcePath == null || targetPath == null) {
throw new IllegalArgumentException("源路径和目标路径不能为空");
}
// 执行复制操作
}
程序执行到 throw 后,当前代码路径会立即中断,控制权交给能够处理该异常的调用层。
throw 与 throws 的区别
| 关键字 | 使用位置 | 作用 |
|---|---|---|
throw | 方法体内部 | 抛出一个具体异常对象 |
throws | 方法或构造器声明后 | 声明可能向调用者传播的异常类型 |
六、自定义异常
当 JDK 提供的异常类型无法清楚表达业务含义时,可以定义业务异常。
自定义受检查异常
public class InvalidEmailAddressException extends Exception {
public InvalidEmailAddressException(String message) {
super(message);
}
}
public class EmailManager {
public void sendEmail(
String recipient,
String title,
String content
) throws InvalidEmailAddressException {
if (recipient == null || recipient.isBlank()) {
throw new InvalidEmailAddressException("收件人地址不能为空");
}
System.out.println("发送邮件成功");
}
}继承 Exception 后,调用者必须捕获或声明该异常。
自定义运行时异常
若异常表示参数错误、业务规则不满足,且不希望强制每一层代码显式捕获,也可以继承 RuntimeException:
public class InvalidEmailAddressException extends RuntimeException {
public InvalidEmailAddressException(String message) {
super(message);
}
}
选择受检查异常还是运行时异常,应结合调用者是否能够恢复、项目异常处理规范以及框架约定决定。
七、异常处理建议
- 捕获能够真正处理的异常,不要无意义地捕获后忽略。
- 优先捕获具体异常,避免直接使用过于宽泛的
Exception。 - 保留原始异常作为
cause,不要丢失问题根因。 - 不要把异常用于普通流程控制。
- 资源优先使用
try-with-resources管理。 - 对外返回稳定的错误信息,对内记录完整异常栈和必要上下文。
喜欢的话,留下你的评论吧~