深入理解异常的抛出与处理
一、异常的抛出
(一)系统自动抛出异常
在程序执行过程中,当遇到一些不符合语言规则或者逻辑错误的情况时,Java 系统会自动抛出异常。例如,进行整数除法运算时,如果除数为 0,系统就会自动抛出 ArithmeticException
异常。
1
2
3
4
5
6
|
public class AutoThrowException {
public static void main(String[] args) {
int result = 10 / 0; // 这里系统会自动抛出ArithmeticException异常
System.out.println("结果是: " + result);
}
}
|
在上述代码中,10 / 0
这一操作不符合数学运算规则,Java 系统检测到这个错误后,会自动创建一个 ArithmeticException
异常对象并抛出。此时,程序的正常执行流程被打断,不会继续执行 System.out.println("结果是: " + result);
这一行代码。
(二)手动抛出异常
有时候,根据业务逻辑的需要,程序员需要手动抛出异常。这通过 throw
关键字来实现。例如,在一个用户注册功能中,要求用户名不能为空。如果检测到用户名为空,就可以手动抛出一个自定义异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class UsernameEmptyException extends RuntimeException {
public UsernameEmptyException(String message) {
super(message);
}
}
public class ManualThrowException {
public static void registerUser(String username) {
if (username == null || username.isEmpty()) {
throw new UsernameEmptyException("用户名不能为空");
}
System.out.println("用户 " + username + " 注册成功");
}
public static void main(String[] args) {
try {
registerUser("");
} catch (UsernameEmptyException e) {
System.out.println("捕获到异常: " + e.getMessage());
}
}
}
|
在 registerUser
方法中,通过 if
语句检查用户名是否为空。如果为空,使用 throw
关键字手动抛出一个 UsernameEmptyException
异常对象。这个异常对象携带了 “用户名不能为空” 这一错误信息。
二、异常的声明抛出
当一个方法内部可能会抛出某种异常,但该方法本身不处理这个异常,而是希望调用它的方法来处理时,就需要在方法声明中使用 throws
关键字声明该方法可能抛出的异常类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import java.io.FileNotFoundException;
import java.io.FileReader;
public class DeclareThrowException {
// 声明该方法可能抛出FileNotFoundException异常
public static void readFile(String filePath) throws FileNotFoundException {
FileReader fileReader = new FileReader(filePath);
// 如果文件不存在,FileReader构造函数会抛出FileNotFoundException异常
}
public static void main(String[] args) {
try {
readFile("nonexistentfile.txt");
} catch (FileNotFoundException e) {
System.out.println("文件不存在异常: " + e.getMessage());
}
}
}
|
在 readFile
方法中,FileReader
的构造函数可能会因为文件不存在而抛出 FileNotFoundException
异常。由于 readFile
方法不处理这个异常,所以在方法声明处使用 throws FileNotFoundException
声明。这样,调用 readFile
方法的代码(如 main
方法)就需要处理这个可能抛出的异常,否则会导致编译错误。
三、异常的捕获与处理
为了避免异常导致程序终止运行,需要使用 try - catch
块来捕获并处理异常。
(一)基本的 try - catch 结构
1
2
3
4
5
6
7
8
9
10
11
|
public class BasicTryCatch {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[3]); // 这里会抛出ArrayIndexOutOfBoundsException异常
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到数组越界异常: " + e.getMessage());
}
System.out.println("程序继续执行...");
}
}
|
在 try
块中放置可能会抛出异常的代码。如果 try
块中的代码抛出了 ArrayIndexOutOfBoundsException
异常,程序会立即跳转到对应的 catch
块中执行。catch
块中的参数 e
是捕获到的异常对象,可以通过它获取异常的相关信息,如错误消息。处理完 catch
块中的代码后,程序会继续执行 try - catch
块之后的代码。
(二)多重 catch 块
当 try
块中的代码可能抛出多种不同类型的异常时,可以使用多个 catch
块来分别处理不同类型的异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class MultipleCatch {
public static void main(String[] args) {
try {
int result = 10 / 0; // 可能抛出ArithmeticException异常
int[] numbers = {1, 2, 3};
System.out.println(numbers[3]); // 可能抛出ArrayIndexOutOfBoundsException异常
} catch (ArithmeticException e) {
System.out.println("捕获到算术异常: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到数组越界异常: " + e.getMessage());
}
System.out.println("程序继续执行...");
}
}
|
在这个例子中,try
块中的代码既有可能抛出 ArithmeticException
异常(除法运算中除数为 0),也有可能抛出 ArrayIndexOutOfBoundsException
异常(数组越界访问)。每个 catch
块负责处理特定类型的异常,程序会根据实际抛出的异常类型跳转到对应的 catch
块执行。
(三)finally 块
finally
块通常与 try - catch
块一起使用,无论 try
块中是否抛出异常,也无论异常是否被 catch
块捕获,finally
块中的代码都会被执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class FinallyBlock {
public static void main(String[] args) {
try {
int result = 10 / 2;
System.out.println("结果是: " + result);
} catch (ArithmeticException e) {
System.out.println("捕获到算术异常: " + e.getMessage());
} finally {
System.out.println("finally块总是会执行");
}
System.out.println("程序继续执行...");
}
}
|
在上述代码中,try
块正常执行没有抛出异常,但 finally
块中的代码依然会被执行。如果 try
块中抛出了异常并且被 catch
块捕获,finally
块同样会在 catch
块执行完毕后执行。
通过理解异常的抛出、声明抛出以及捕获处理机制,可以更好地编写健壮的 Java 程序,使其能够应对各种可能出现的错误情况,提高程序的稳定性和可靠性。