JAVA异常

JAVA异常

异常的基本概念

异常(Exception)是指程序在运行时发生的不被期望的事件,它阻止了程序按照预期正常执行。需要注意的是,编译时期出现的错误(如语法错误)通常称为编译错误,不属于异常处理的范畴。异常处理机制旨在让程序在运行时遇到错误时,能够按照预先设定的逻辑进行应对,从而尽可能恢复或优雅终止。

异常的分类

Java中的异常和错误都继承自 Throwable类,其层次结构如下

异常

Error (错误): 是程序无法处理的严重问题,通常与JVM本身或系统资源相关,应用程序不应尝试捕获处理。例如 OutOfMemoryError(内存耗尽)、StackOverflowError(栈溢出,常由无限递归引发)等

Exception (异常): 是程序本身可以并且应该处理的问题。它分为两大类:

受检异常 (Checked Exception): 指在编译时期就必须被处理的异常。如果方法可能抛出这类异常,必须在方法签名中用 throws声明,或者用 try-catch块进行捕获,否则代码无法通过编译。例如 IOException(输入输出异常)、SQLException(SQL相关异常)

非受检异常 (Unchecked Exception): 主要指 RuntimeException及其子类。它们在编译时期不强制要求处理,通常是由程序逻辑错误引发的,应在开发阶段通过代码修正来避免。例如 NullPointerException(空对象异常)、ArrayIndexOutOfBoundsException(数组越界异常)、ArithmeticException(如除以零)

如何处理异常

Java提供了 trycatchfinallythrowthrows关键字来处理异常

1. 捕获异常 (try-catch-finally)

基本语法结构如下:

1
2
3
4
5
6
7
8
9
10
try {
// 可能会发生异常的代码
} catch (ExceptionType1 e1) {
// 捕获并处理特定类型 ExceptionType1 的异常
} catch (ExceptionType2 e2) {
// 捕获并处理特定类型 ExceptionType2 的异常
} finally {
// 无论是否发生异常,最终都会执行的代码块
// 通常用于释放资源(如关闭文件、数据库连接等)
}

示例:处理数组越界异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HelloWorld {
public static void main(String[] args) {
try {
int[] a = new int[5];
for (int i = 0; i <= 6; i++) { // 循环最后一次 i=6 时会越界
a[i] = i;
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到数组越界异常: " + e.getMessage());
} finally {
System.out.println("这段代码总是会执行。");
} // 在多异常检查中,finally为所有异常提供了统一出口,一般用于关闭资源
System.out.println("Hello World"); // 由于异常被捕获,程序可以继续执行
}
}

2. 声明抛出异常 (throws)

在方法签名中使用 throws关键字,声明该方法可能抛出的异常,将异常的处理责任交给方法的调用者。这常用于受检异常(Checked Exception)。

1
2
3
public void readFile() throws IOException {
// 可能抛出IOException的代码
}

3. 主动抛出异常 (throw)

使用 throw关键字在代码中主动抛出一个异常对象(可以是内置异常或自定义异常)。

1
2
3
if (divisor == 0) {
throw new ArithmeticException("除数不能为零");
}

4. try-with-resources(自动资源管理)

对于实现了 AutoCloseable接口的资源(如 InputStream, OutputStream, Connection等),强烈推荐使用 try-with-resources 语句。它可以自动关闭资源,代码更简洁,且不易出错,无需显式编写 finally块来关闭资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.*;

public class TryWithResourcesExample {
public static void main(String[] args) {
// 资源在try后的括号内声明和初始化,多个资源用分号隔开
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println("Line => " + line);
}
} catch (IOException e) { // 这里仍然需要捕获可能发生的IOException
System.out.println("读取文件时发生异常: " + e.getMessage());
} // 无需finally块,br会自动关闭
}
}

自定义异常

当Java标准异常类无法清晰表达特定的业务逻辑错误时,可以创建自定义异常

创建自定义异常的最佳实践

通常继承自 Exception(创建受检异常)或 RuntimeException(创建非受检异常)。选择取决于错误性质:是否强制调用者处理

提供多个构造方法,通常至少包括一个带详细消息(String message)的构造方法,并调用父类构造器

可以添加自定义字段(如错误码)来提供更丰富的错误信息

示例:自定义一个非受检的业务异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 自定义异常类,继承RuntimeException成为非受检异常
public class InsufficientBalanceException extends RuntimeException {
private double currentBalance;
private double amountRequired;

// 构造方法
public InsufficientBalanceException(String message, double currentBalance, double amountRequired) {
super(message); // 调用父类构造方法
this.currentBalance = currentBalance;
this.amountRequired = amountRequired;
}

// 获取额外信息的方法
public double getCurrentBalance() {
return currentBalance;
}

public double getAmountRequired() {
return amountRequired;
}

// 可选:重写toString以提供更详细的错误信息
@Override
public String toString() {
return super.toString() +
" Current Balance: " + currentBalance +
", Amount Required: " + amountRequired;
}
}

// 使用自定义异常
public class BankAccount {
private double balance;

public void withdraw(double amount) {
if (amount > balance) {
throw new InsufficientBalanceException("余额不足", balance, amount);
}
balance -= amount;
}
}