Menu
CoddyTech

已检查异常与未检查异常

本课属于 Coddy Java 学习旅程的“面向对象编程”单元。第 61 课,共 87 课。

你已经了解,Java 的异常层次结构分成了不同的分支。你需要理解的关键区别是 受检异常和非受检异常。这会影响你在代码中必须如何处理它们。

受检异常是编译器会强制你处理的异常。它们直接继承自 Exception(而不是通过 RuntimeException)。你必须捕获它们,或使用 throws 声明:

// 必须处理 IOException,因为它是受检异常
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);  // 可能抛出 IOException
}

// 或者捕获它
public void readFileSafe(String path) {
    try {
        FileReader reader = new FileReader(path);
    } catch (IOException e) {
        System.out.println("File error: " + e.getMessage());
    }
}

未检查异常继承自 RuntimeException。编译器不会要求你处理它们。它们通常表示编程错误,例如空引用或数组访问无效:

// 不需要声明 throws
public int divide(int a, int b) {
    return a / b;  // 可能抛出 ArithmeticException(非受检异常)
}

String name = null;
name.length();  // NullPointerException(非受检异常)

创建自定义异常时,根据你希望调用方如何处理它们来选择父类。对于调用方应预料到且可以恢复的情况,使用受检异常。对于正确代码中不应出现的编程错误,使用非受检异常。

challenge icon

挑战

简单

让我们构建一个年龄验证系统,展示已检查异常和未检查异常之间的主要区别!你将创建这两种类型的自定义异常,并了解编译器如何区别对待它们。

你将把代码分成 4 个文件:

  • InvalidAgeException.java:创建一个用于年龄验证失败的已检查异常。这个异常应直接继承 Exception,这意味着调用方必须处理它。添加一个接收消息并将其传递给父类的构造函数。
  • NegativeAgeException.java:创建一个未检查异常,用于处理有人输入负年龄的情况(这是输入正确时不应发生的编程错误)。它应继承 RuntimeException。添加一个接收消息并将其传递给父类的构造函数。
  • AgeVerifier.java:创建一个使用这两种异常类型验证年龄的类。添加两个静态方法:

    verifyAge(int age):如果年龄小于 0,此方法应抛出带有消息 "Age cannot be negative: [age]" 的未检查异常 NegativeAgeException。如果年龄小于 18,则抛出带有消息 "Must be 18 or older: [age]" 的已检查异常 InvalidAgeException。否则,输出 Age [age] verified successfully。由于此方法会抛出已检查异常,你必须使用 throws InvalidAgeException 声明它。

    verifyAgeUncheckedOnly(int age):此方法只使用未检查异常。如果年龄为负数,则使用相同的消息格式抛出 NegativeAgeException。如果年龄小于 18,则抛出带有消息 "Too young: [age]" 的 IllegalArgumentException。否则,输出 Age [age] verified (unchecked method)。注意,此方法不需要 throws 声明!

  • Main.java:把年龄验证系统组合起来!你将接收一个输入:年龄(整数)。

    首先,输出 === Checked Exception Method ===,并使用输入的年龄调用 verifyAge。由于它会抛出已检查异常,你必须用 try-catch 包裹它。捕获 InvalidAgeException,并输出 Checked exception caught: [message]。还要捕获 NegativeAgeException,并输出 Unchecked exception caught: [message]。

    然后输出一个空行和 === Unchecked Exception Method ===。使用相同的年龄调用 verifyAgeUncheckedOnly。尽管此方法可能抛出异常,你并不一定要捕获它们。不过,还是要用 try-catch 包裹它,以便妥善处理错误。捕获 NegativeAgeException,并输出 Runtime exception caught: [message]。捕获 IllegalArgumentException,并输出 Illegal argument caught: [message]。

你将收到一个输入:表示待验证年龄的整数。

注意这个关键区别:带有已检查异常的方法必须使用 throws 声明,并要求调用方处理该异常;而未检查异常方法即使没有任何异常处理代码,也能正常编译。这就是 Java 中已检查异常和未检查异常的根本区别!

亲手试试

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int age = scanner.nextInt();
        
        // TODO:测试受检异常方法
        // 输出:"=== Checked Exception Method ==="
        // 在 try-catch 中调用 AgeVerifier.verifyAge(age)
        // 捕获 InvalidAgeException:输出 "Checked exception caught: [message]"
        // 捕获 NegativeAgeException:输出 "Unchecked exception caught: [message]"
        
        
        // TODO:先输出一个空行,再输出 "=== Unchecked Exception Method ==="
        // 在 try-catch 中调用 AgeVerifier.verifyAgeUncheckedOnly(age)
        // 捕获 NegativeAgeException:输出 "Runtime exception caught: [message]"
        // 捕获 IllegalArgumentException:输出 "Illegal argument caught: [message]"
        
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Java 在线编译器