跳转至

12 异常处理

本章目标

  • 理解什么是异常(Exception)
  • 掌握 try-catch-finally
  • 掌握 throw 与 throws
  • 了解常见异常类型

12.1 什么是异常?

程序运行时出了错,Java 会抛出一个异常对象。不处理的话,程序直接崩溃退出。

int[] arr = {1, 2, 3};
System.out.println(arr[5]);   // 运行时抛 ArrayIndexOutOfBoundsException

不处理时的输出:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
    at Main.main(Main.java:3)

学会读异常信息是程序员的基本功:

异常类型: ArrayIndexOutOfBoundsException
错误描述: Index 5 out of bounds for length 3
出错位置: Main.main(Main.java:3)   ← 第 3 行

12.2 try-catch 捕获异常

try {
    // 可能出错的代码
    int result = 10 / 0;          // 除零
    System.out.println(result);
} catch (ArithmeticException e) {
    // 捕获后处理
    System.out.println("数学错误: " + e.getMessage());
}

System.out.println("程序继续运行…");   // ✅ 不会崩溃

多 catch

try {
    int[] arr = null;
    arr[0] = 1;
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组越界");
} catch (NullPointerException e) {
    System.out.println("空指针");
}

多个异常类型相同处理,可以用 |:

} catch (IOException | SQLException e) {
    System.out.println("IO 或 SQL 错误");
}

12.3 finally:无论如何都会执行

try {
    // 读文件
} catch (IOException e) {
    System.out.println("读取失败");
} finally {
    System.out.println("清理资源");   // 无论成功失败都执行
}

经典用途:关闭文件、释放连接。不过现在资源类推荐 try-with-resources(第 18 章 IO 讲):

try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
    // 用 br 读文件,用完自动关闭
} catch (IOException e) {
    System.out.println("读取失败: " + e.getMessage());
}

12.4 异常体系

Throwable(所有错误/异常的根)
 ├── Error          严重错误(内存溢出等,不处理)
 │    └── OutOfMemoryError、StackOverflowError
 └── Exception      异常(要处理)
      ├── RuntimeException       运行时异常(不强制处理)
      │    ├── NullPointerException
      │    ├── ArrayIndexOutOfBoundsException
      │    ├── ArithmeticException
      │    └── ClassCastException
      └── 受检异常(编译器强制处理)
           ├── IOException
           ├── SQLException
           └── ...

运行时异常 vs 受检异常

运行时异常 受检异常
编译器要求处理 不要求 必须 try-catch 或 throws
例子 空指针、越界 文件读写、网络
说明 代码逻辑问题 外部条件问题(文件不存在等)
// 受检异常:编译器直接报错"未处理"
BufferedReader br = new BufferedReader(new FileReader("a.txt"));  // ❌ 必须处理 IOException

12.5 throw 与 throws

throw:手动抛出异常

public void setLevel(int level) {
    if (level < 1) {
        throw new IllegalArgumentException("等级不能小于 1");
    }
    this.level = level;
}

throws:声明方法可能抛出异常

public void readConfig() throws IOException {
    // 方法内部不处理,抛给调用者
    FileReader fr = new FileReader("config.json");
}

// 调用者必须处理
try {
    readConfig();
} catch (IOException e) {
    System.out.println("配置文件读取失败");
}

12.6 自定义异常

public class NotEnoughMoneyException extends RuntimeException {
    public NotEnoughMoneyException(String message) {
        super(message);
    }
}

// 使用
public void buy(int price, int money) {
    if (money < price) {
        throw new NotEnoughMoneyException("余额不足,还差 " + (price - money));
    }
}

12.7 常见坑

1. 空指针 NPE

String s = null;
s.length();    // NPE!

// 防御写法
if (s != null && s.length() > 0) { ... }

2. 异常被吞掉

try {
    doSomething();
} catch (Exception e) {
    // 什么都不写 ← 极其不推荐!出错完全没痕迹
}

// 至少要打日志
catch (Exception e) {
    System.out.println("出错了: " + e.getMessage());
    e.printStackTrace();   // 打印完整堆栈
}

3. catch 范围过大

catch (Exception e) { ... }   // 尽量 catch 具体类型

12.8 实战:模组场景

// 加载模组配置文件
public Map<String, Object> loadConfig(Path path) {
    try {
        String json = Files.readString(path);
        return gson.fromJson(json, Map.class);
    } catch (IOException e) {
        System.out.println("[MyMod] 配置读取失败,使用默认配置: " + e.getMessage());
        return new HashMap<>();
    } catch (JsonSyntaxException e) {
        System.out.println("[MyMod] 配置格式错误: " + e.getMessage());
        return new HashMap<>();
    }
}

12.9 小练习

  1. 故意制造 3 种运行时异常(空指针、越界、除零),观察报错信息
  2. 用 try-catch 捕获除零异常,程序不崩溃继续运行
  3. 自定义一个 TooHotException,当温度 > 100 时抛出,main 里捕获
  4. 写一个方法用 throws 声明受检异常,并在调用处 try-catch

本章小结

  • 异常 = 程序出错的信号;不处理会崩溃
  • try-catch-finally 捕获处理;try-with-resources 自动关资源
  • 运行时异常不强制处理,受检异常必须处理
  • throw 抛异常,throws 声明异常
  • 别吞异常,至少打日志

下一章:13 泛型

🏠 返回总站