异常处理机制
掌握 try-catch-finally、throws、自定义异常
异常处理
程序不可能永远正常运行——文件不存在、网络断开、用户输入非法……异常处理就是给程序加上"安全带",让它在出问题时优雅处理而不是直接崩溃。
学完本章你将: 掌握异常体系、try-catch-finally、throws、try-with-resources、自定义异常。
异常体系 —— 两大类
Java 把异常分为两类,核心区别在编译时是否强制处理:
Throwable(所有异常的祖宗)
├── Error — 严重系统错误(OOM、StackOverflow),程序无法处理,只能认栽
└── Exception
├── RuntimeException — 运行时异常(NPE、数组越界),不强制处理
└── 受检异常(Checked)— 编译时必须 try-catch 或 throws,如 IOException
| 类型 | 示例 | 是否强制处理 | 典型原因 |
|---|---|---|---|
| Error | `OutOfMemoryError` | ❌ | JVM 资源耗尽 |
| RuntimeException | `NullPointerException` | ❌ | 代码逻辑 bug |
| 受检异常 | `IOException`、`SQLException` | ✅ | 外部因素(文件、网络) |
💡 设计思想: 受检异常是"可预见的外部问题",编译器强迫你提前想好怎么应对。运行时异常是"代码写错了",应该修代码而不是捕获。
try-catch-finally
try 包裹可能出错的代码。catch 按异常类型匹配——子类异常必须写在父类前面,否则子类永远匹配不到。finally 无论是否异常都会执行,通常用来释放资源。
try {
int[] arr = {1, 2, 3};
System.out.println(arr[10]); // 数组越界→跳入 catch
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("下标越界:" + e.getMessage());
} finally {
System.out.println("无论如何我都执行——比如关闭文件");
}
// 多个 catch:先子类后父类
try {
String s = null;
s.length();
} catch (NullPointerException e) { // 子类在前
System.out.println("空指针!");
} catch (Exception e) { // 父类在后(兜底)
System.out.println("其他异常:" + e);
}
throws —— 把锅甩给调用者
方法内部不处理异常时,可以用 throws 声明"这个方法可能抛出这些异常,调用者你来处理"。
// 声明:我可能抛出 IOException,你来解决
public static String readFirstLine(String path) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(path));
String line = reader.readLine();
reader.close();
return line;
}
// 调用者:要么 try-catch,要么继续往上 throws
try {
String line = readFirstLine("test.txt");
} catch (IOException e) {
System.err.println("读文件失败:" + e.getMessage());
}
try-with-resources —— 自动关闭
finally 中手动关闭资源容易忘、容易写错。Java 7+ 的 try-with-resources 会自动调用 close(),管它是正常结束还是抛异常。
// try (资源声明) —— 出了这个块自动 close,无论是否异常
try (
BufferedReader reader = new BufferedReader(new FileReader("in.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("out.txt"))
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line.toUpperCase());
writer.newLine();
}
} catch (IOException e) {
System.err.println("处理文件出错:" + e.getMessage());
}
// 不用写 finally reader.close() writer.close() 了!
自定义异常
内置异常有时语义不够明确,自定义异常让错误更有可读性。
// 继承 Exception(受检)或 RuntimeException(非受检)
public class InsufficientFundsException extends Exception {
private final double deficit; // 差多少钱
public InsufficientFundsException(double deficit) {
super("余额不足,还差 " + deficit + " 元");
this.deficit = deficit;
}
public double getDeficit() { return deficit; }
}
// 使用
public void withdraw(double amount) throws InsufficientFundsException {
if (balance < amount) {
throw new InsufficientFundsException(amount - balance);
}
balance -= amount;
}
💡 经验: 大多数场景用已有的异常类就够了。需要自定义时,优先继承
RuntimeException(不强制 try-catch,更灵活)。
public double getDeficit() { return deficit; }
}
// 使用 public void withdraw(double amount) throws InsufficientFundsException { if (balance < amount) { throw new InsufficientFundsException(amount - balance); } balance -= amount; }