源本科技 | 码上会

Java 异常处理机制

2026/01/22
65
0

学习目标

  • 理解 异常(Exception) 的概念及其在程序执行中的作用

  • 掌握 trycatchfinally 三大核心关键字的使用方式

  • 区分 检查型异常非检查型异常

  • 能够使用 throw 主动抛出异常,使用 throws 声明方法可能抛出的异常

  • 了解 Java 异常类的继承体系及 JVM 的异常处理流程

  • 能够设计自定义异常以满足特定业务需求


什么是异常

在 Java 中,异常是程序执行过程中发生的意外事件,它会中断正常的指令流。异常处理机制允许我们在不终止整个程序的前提下,优雅地应对错误。

异常 ≠ 错误(Error):异常通常可恢复,而错误(如内存溢出)往往不可恢复。


基本异常处理结构

try-catch

示例:除零异常处理

public class MainApp {
    public static void main(String[] args) {
        int numerator = 10;
        int denominator = 0;

        try {
            int result = numerator / denominator; // 可能抛出 ArithmeticException
            System.out.println("结果: " + result);
        } catch (ArithmeticException e) {
            System.out.println("错误:不能除以零!");
        }
    }
}
错误:不能除以零!

执行流程

  1. JVM 执行 try 块中的代码;

  2. 若发生异常,立即跳转到匹配的 catch 块;

  3. 执行完 catch 后,继续执行后续代码(程序不会崩溃)。


确保资源释放

无论是否发生异常,finally总是执行,常用于关闭文件、数据库连接等资源清理操作。

示例:数组越界与 finally

public class FinallyExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};

        try {
            System.out.println(numbers[5]); // 抛出 ArrayIndexOutOfBoundsException
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("捕获异常: " + e.getMessage());
        } finally {
            System.out.println("finally 块始终执行。");
        }

        System.out.println("程序继续运行...");
    }
}
捕获异常: Index 5 out of bounds for length 3
finally 块始终执行。
程序继续运行...

注意:即使 trycatch 中有 returnfinally 仍会执行(除非调用 System.exit())。


throwthrows

主动抛出异常

throw 用于在代码中显式触发异常。

class AgeValidator {
    static void checkAge(int age) {
        if (age < 18) {
            throw new IllegalArgumentException("年龄必须大于或等于 18 岁");
        }
        System.out.println("年龄验证通过");
    }

    public static void main(String[] args) {
        try {
            checkAge(15);
        } catch (IllegalArgumentException e) {
            System.out.println("异常信息: " + e.getMessage());
        }
    }
}
异常信息: 年龄必须大于或等于 18 岁

建议:使用语义更准确的异常类型(如 IllegalArgumentException),而非 ArithmeticException


声明方法可能抛出的异常

throws 主要用于检查型异常,告知调用者需处理该异常。

import java.io.*;

class FileReaderUtil {
    // 声明该方法可能抛出 IOException
    static void readFile(String filename) throws IOException {
        FileReader reader = new FileReader(filename); // 可能抛出 FileNotFoundException
        // ...读取文件逻辑
        reader.close();
    }

    public static void main(String[] args) {
        try {
            readFile("nonexistent.txt");
        } catch (IOException e) {
            System.out.println("文件操作失败: " + e.getMessage());
        }
    }
}

输出(若文件不存在):

文件操作失败: nonexistent.txt (No such file or directory)

规则:若方法调用了一个声明 throws 检查型异常的方法,且未在内部 catch,则自身也必须声明 throws


异常体系结构

所有异常和错误都继承自 Throwable 类:

Throwable
├── Error                 // 严重系统错误,通常不可恢复(如 OutOfMemoryError)
└── Exception
    ├── RuntimeException  // 非检查型异常(如 NullPointerException, ArithmeticException)
    └── 其他 Exception     // 检查型异常(如 IOException, SQLException)

特性

检查型异常(Checked)

非检查型异常(Unchecked)

编译期检查

✅ 必须处理(try-catch 或 throws)

❌ 不强制处理

常见类型

IOException, SQLException

NullPointerException, ArrayIndexOutOfBoundsException

继承关系

继承 Exception 但不继承 RuntimeException

继承 RuntimeException


多异常捕获与嵌套

多个 catch

try {
    // 可能抛出多种异常的代码
    int[] arr = new int[3];
    arr[5] = 10;           // ArrayIndexOutOfBoundsException
    String s = null;
    s.length();            // NullPointerException
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组越界: " + e.getMessage());
} catch (NullPointerException e) {
    System.out.println("空指针异常: " + e.getMessage());
}

注意:子类异常应放在父类异常之前(否则编译报错)。


嵌套 try-catch

public class NestedTryExample {
    public static void main(String[] args) {
        try {
            System.out.println("外层 try");
            try {
                int a = 10 / 0; // ArithmeticException
            } catch (ArithmeticException e) {
                System.out.println("内层捕获: " + e.getMessage());
            }
            String str = null;
            str.length(); // NullPointerException
        } catch (NullPointerException e) {
            System.out.println("外层捕获: " + e.getMessage());
        }
    }
}
外层 try
内层捕获: / by zero
外层捕获: Cannot invoke "String.length()" because "<local1>" is null

异常信息输出方法

方法

作用

e.printStackTrace()

打印完整堆栈跟踪(含行号)

e.toString()

返回 "异常类名: 描述"

e.getMessage()

仅返回异常描述信息

try {
    int x = 1 / 0;
} catch (ArithmeticException e) {
    System.out.println("toString(): " + e.toString());
    System.out.println("getMessage(): " + e.getMessage());
    e.printStackTrace(); // 通常用于调试
}

JVM 如何处理异常

  1. 异常发生时,JVM 创建异常对象并记录当前调用栈(Call Stack)

  2. 从当前方法开始,向上传递调用栈,寻找匹配的 catch 块。

  3. 若找到,执行对应 catchfinally

  4. 若未找到,交由 默认异常处理器:打印堆栈并终止程序。

示例:未捕获的异常

public class UnhandledException {
    public static void main(String[] args) {
        String s = null;
        System.out.println(s.length()); // 抛出 NullPointerException
        System.out.println("这行不会执行");
    }
}

输出

Exception in thread "main" java.lang.NullPointerException: ...
    at UnhandledException.main(...)

异常 vs 错误

特性

异常(Exception)

错误(Error)

可恢复性

✅ 通常可处理

❌ 一般不可恢复

来源

程序逻辑或外部输入

JVM 内部问题

示例

IOException, NumberFormatException

OutOfMemoryError, StackOverflowError

处理建议

使用 try-catch

优化代码或增加资源,而非捕获


重点总结

  • 使用 try-catch-finally 结构安全处理运行时错误。

  • finally 用于资源清理,几乎总被执行。

  • throw 用于主动抛出异常,throws 用于声明方法可能抛出的检查型异常。

  • 检查型异常必须处理,非检查型异常可选处理。

  • 自定义异常可通过继承 ExceptionRuntimeException 实现。

  • 异常处理的目标是提升程序健壮性,而非掩盖错误。


思考题

  1. 为什么 FileNotFoundException 是检查型异常,而 NullPointerException 是非检查型异常?这种设计背后的哲学是什么?

  2. finally 块中修改返回值是否会影响方法的最终返回结果?请编写代码验证。

  3. 如何设计一个自定义异常 InvalidEmployeeIdException,并在员工管理系统中使用它来验证 ID 格式?