已检查异常与未检查异常
本课属于 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(非受检异常)创建自定义异常时,根据你希望调用方如何处理它们来选择父类。对于调用方应预料到且可以恢复的情况,使用受检异常。对于正确代码中不应出现的编程错误,使用非受检异常。
挑战
简单让我们构建一个年龄验证系统,展示已检查异常和未检查异常之间的主要区别!你将创建这两种类型的自定义异常,并了解编译器如何区别对待它们。
你将把代码分成 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]"
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Java 在线编译器