往期回顾
目录
什么是异常处理?
Java中的异常处理是一种用于处理程序执行期间错误的机制。它允许开发者编写能够优雅地处理错误情况的健壮代码。Java的异常处理基于“抛出-捕获”模型,即当发生错误时,会创建一个表示该错误的异常对象,并将其从错误发生的地方抛出;然后,这个异常可以由专门的代码块(称为异常处理器)捕获并处理。
异常分类
检查型异常
检查型异常(Checked Exception)是指在编译时期就需要被捕获或声明的异常。这些异常是那些通常可以通过合理的编程逻辑来预防或处理的异常。如果方法可能抛出检查型异常,而调用该方法的代码没有捕获(try-catch)这个异常,或者没有在方法签名中通过throws
关键字声明这个异常,编译器将报错。
以下是一些常见的检查型异常:
- IOException:处理输入输出操作时可能抛出的异常,例如读写文件或网络通信。
- SQLException:处理数据库操作时可能抛出的异常,例如执行SQL语句时出错。
- ClassNotFoundException:使用
Class.forName()
方法动态加载类时,如果找不到指定的类,则抛出此异常。 - InterruptedException:当一个线程在等待、睡眠或尝试执行一个阻塞操作时,另一个线程中断了当前线程,则抛出此异常。
- ParseException:解析字符串为日期、数字等时,如果字符串格式不正确,则抛出此异常(具体实现可能因类库而异,例如
java.text.ParseException
)。 - InvocationTargetException:在使用反射调用方法时,如果底层方法抛出异常,则通过此异常包装并抛出。
- NoSuchMethodException 和 IllegalAccessException:在使用反射访问方法或字段时,如果找不到指定的方法或没有访问权限,则抛出这些异常。
- FileAlreadyExistsException:在创建新文件时,如果文件已经存在,则抛出此异常(具体实现可能因类库而异,例如
java.nio.file.FileAlreadyExistsException
)。 - SocketException:处理网络套接字操作时可能抛出的异常,例如连接失败或套接字错误。
- UnknownHostException:根据主机名解析IP地址时,如果无法找到主机名对应的IP地址,则抛出此异常。
非检查型异常(Unchecked Exceptions)
非检查型异常(Unchecked Exception)通常指的是RuntimeException
及其子类,以及Error
类及其子类。这些异常在编译时不会被强制要求处理,即可以不使用try-catch
块捕获,也不需要在方法签名中通过throws
关键字声明。然而,尽管编译器不强制要求处理这些异常,但在实际编程中,合理地处理这些异常仍然是非常重要的,以保证程序的健壮性和稳定性。下面会列举下常见的RuntimeException
和Error
。
运行时异常(RuntimeException
)
NullPointerException
:- 当程序尝试在需要对象实例的地方使用
null
时抛出。 - 例如:
String s = null; s.length();
- 当程序尝试在需要对象实例的地方使用
ArrayIndexOutOfBoundsException
:- 当数组索引超出其有效范围时抛出。
- 例如:
int[] array = {1, 2, 3}; int value = array[3];
IllegalArgumentException
:- 当传递给方法的参数不合适时抛出。
- 例如:
public void divide(int a, int b) { if (b == 0) throw new IllegalArgumentException("除数不能为零"); }
IllegalStateException
:- 当对象处于不适当的状态时抛出。
- 例如:在一个尚未初始化的对象上调用某些方法。
ArithmeticException
:- 当发生算术错误时抛出,如除以零。
- 例如:
int result = 1 / 0;
ClassCastException
:- 当尝试将对象强制转换为不兼容的类型时抛出。
- 例如:
Object obj = new String("Hello"); Integer i = (Integer) obj;
NumberFormatException
:- 当尝试将字符串转换为数字但格式不正确时抛出。
- 例如:
int number = Integer.parseInt("abc");
IndexOutOfBoundsException
:- 当索引超出有效范围时抛出,类似于
ArrayIndexOutOfBoundsException
,但更通用。 - 例如:
List<String> list = new ArrayList<>(); list.get(0);
- 当索引超出有效范围时抛出,类似于
StringIndexOutOfBoundsException
:- 当字符串索引超出有效范围时抛出。
- 例如:
String s = "hello"; char c = s.charAt(5);
NegativeArraySizeException
:- 当尝试创建具有负大小的数组时抛出。
- 例如:
int[] array = new int[-1];
UnsupportedOperationException
:- 当请求的操作不支持时抛出。
- 例如:在不可修改的集合上尝试添加元素。
SecurityException
:- 当安全策略被违反时抛出。
- 例如:尝试执行受限制的操作。
ConcurrentModificationException
:- 当检测到并发修改时抛出,通常发生在迭代器遍历集合时集合被修改。
- 例如:
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); for (String s : list) { list.remove(s); }
错误(Error)
Error
类及其子类表示严重的系统级问题,这些问题通常表示虚拟机(JVM)本身或系统资源的问题。与异常不同,错误通常是不可恢复的,因此通常不需要在代码中捕获和处理这些错误。然而,了解常见的错误类型有助于理解程序中可能出现的问题。
以下是一些常见的 Error
类及其子类:
OutOfMemoryError
:- 当Java虚拟机无法分配新的对象,因为没有足够的内存可用时抛出。
- 例如:创建一个非常大的数组或大量对象时,可能会导致此错误。
StackOverflowError
:- 当一个方法调用栈深度超过虚拟机限制时抛出。
- 通常由无限递归引起。
NoClassDefFoundError
:- 当运行时系统试图加载一个类,但在类路径中找不到该类的定义时抛出。
- 通常是因为类路径配置错误或依赖库缺失。
LinkageError
:- 表示某些链接操作失败,例如类的定义在加载时发生了不一致。
- 子类包括
IncompatibleClassChangeError
、NoSuchFieldError
和NoSuchMethodError
。
IncompatibleClassChangeError
:- 当类的定义在加载时与其先前的定义不兼容时抛出。
- 例如,一个接口在加载时变成了一个类。
NoSuchFieldError
:- 当试图访问一个不存在的字段时抛出。
- 通常是因为类的结构在编译和运行时发生了变化。
NoSuchMethodError
:- 当试图调用一个不存在的方法时抛出。
- 通常是因为类的结构在编译和运行时发生了变化。
AssertionError
:- 当断言失败时抛出。
- 断言通常用于调试目的,确保某些条件在运行时为真。
ThreadDeath
:- 当线程被终止时抛出。
- 通常由
Thread.stop()
方法调用引起,但不推荐使用该方法。
UnknownError
:- 表示一个未知的错误,通常表示JVM内部的问题。
VirtualMachineError
:- 表示JVM本身出现问题。
- 子类包括
OutOfMemoryError
和StackOverflowError
。
异常处理的主要语法
- try 块:用于封装可能抛出异常的代码。如果在
try
块中发生异常,则控制将转移到与之匹配的catch
块。 - catch 块:用于捕获
try
块中抛出的异常。每个try
块可以有一个或多个catch
块,用来指定可以捕获哪些类型的异常。 - finally 块:无论是否发生异常,
finally
块中的代码都会被执行。通常用于释放资源,如关闭文件或网络连接等。 - throw 关键字:用于显式抛出一个异常对象。
- throws 关键字:用于声明一个方法可能会抛出的异常。这可以是编译器强制要求的方法(检查型异常),也可以是可选声明的(运行时异常)。
- 自定义异常:可以通过继承
Exception
或其子类来创建自己的异常类型。
实战案例
下面是一个完整的Java异常处理案例,它展示了如何使用try
、catch
、finally
、throws
和throw
关键字来处理异常。这个例子模拟了一个简单的文件读取操作,同时包含了一些自定义异常的处理。
示例代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FileReadExample {
public static void main(String[] args) {
try {
readAndProcessFile("example.txt");
} catch (FileNotFoundException e) {
System.err.println("文件未找到: " + e.getMessage());
} catch (IOException e) {
System.err.println("读取文件时发生错误: " + e.getMessage());
} catch (CustomException e) {
System.err.println("自定义异常: " + e.getMessage());
}
}
public static void readAndProcessFile(String fileName) throws FileNotFoundException, IOException, CustomException {
BufferedReader reader = null;
try {
// 尝试打开文件
reader = new BufferedReader(new FileReader(fileName));
String line;
// 逐行读取文件内容
while ((line = reader.readLine()) != null) {
processLine(line);
}
} catch (FileNotFoundException e) {
// 文件未找到时的处理
throw e; // 重新抛出异常
} catch (IOException e) {
// 读取文件时发生其他I/O错误
throw e;
} catch (CustomException e) {
// 处理自定义异常
throw e;
} finally {
// 无论是否发生异常,都会执行finally块
if (reader != null) {
try {
reader.close(); // 关闭文件流
} catch (IOException e) {
System.err.println("关闭文件流时发生错误: " + e.getMessage());
}
}
}
}
public static void processLine(String line) throws CustomException {
// 模拟处理每一行数据
if (line == null || line.isEmpty()) {
throw new CustomException("空行或无效行: " + line);
}
System.out.println("处理行: " + line);
}
}
//自定义异常
class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
代码解释
- 导入必要的类:
BufferedReader
和FileReader
是用于文件读取的类。IOException
是一个检查型异常,用于处理输入输出操作中的错误。
- 主方法 (
main
方法):- 调用
readAndProcessFile
方法来读取和处理文件。 - 使用
try
块来包裹可能抛出异常的代码。 - 捕获并处理
FileNotFoundException
、IOException
和CustomException
。
- 调用
readAndProcessFile
方法:- 创建一个
BufferedReader
对象,用于读取文件。 - 使用
try
块来包裹可能抛出异常的代码。 - 捕获并处理
FileNotFoundException
、IOException
和CustomException
。 - 使用
throw
关键字重新抛出异常,以便在main
方法中进一步处理。 - 使用
finally
块来确保文件流在任何情况下都被关闭。
- 创建一个
processLine
方法:- 模拟处理每一行数据。
- 如果行为空或无效,抛出自定义异常
CustomException
。
- 自定义异常类 (
CustomException
):- 继承自
Exception
类。 - 提供一个构造函数来设置异常消息。
- 继承自
处理异常的最佳实践
- 尽量捕获具体的异常:避免捕获所有异常(如
catch (Exception e)
),而应尽可能捕获具体的异常类型,这样可以更准确地处理特定错误。 - 不要忽略异常:捕获到异常后应该做适当的处理,而不是简单地忽略它。
- 使用 finally 块释放资源:确保无论是否发生异常,都能正确地释放资源。
- 记录异常信息:在捕获异常后,记录异常信息可以帮助后续的问题排查。
- 避免过度使用异常:异常处理机制虽然强大,但不应该用作常规流程控制结构。过度使用异常会影响程序性能。