在Java编程中, 异常处理 是一项至关重要的技能,让我们能够有效地应对程序运行过程中可能出现的各种 错误状况 ,从而使程序更具健壮性。

什么是异常?
Java 异常 是 程序运行时 出现的问题或错误的表示,代表了程序正常的控制流程被中断的情况。Java将异常分为两大类:checked异常和unchecked异常(也称运行时异常)。
|
分类 |
描述 |
|
Checked Exception |
在编译阶段 就需要程序员处理,如果不处理或声明抛出,编译器将拒绝编译。例如,当你试图打开一个不存在的文件时,Java会抛出 java.io.FileNotFoundException ,这是一种checked异常。 |
|
Unchecked Exception |
在运行时 可能发生,但编译器不要求我们必须处理。最常见的unchecked异常是 java.lang.NullPointerException ,当我们试图访问一个 null 对象的属性或方法时会出现此类异常。 |
Java异常处理机制
Java使用 try catch finally 语句结构来处理异常。
try {
// 可能抛出异常的代码放在这里
File file = new File("file.txt");
FileReader reader = new FileReader(file);
} catch (FileNotFoundException e) {
// 当在try块中抛出FileNotFoundException时,这里的代*会码**被执行
System.out.println("文件未找到:" + e.getMessage());
} finally {
// 不论是否发生异常,finally块中的代码总会被执行
// 这里通常用于资源清理,如关闭文件流
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,尝试打开一个文件,如果文件不存在,就会触发 FileNotFoundException ,并在catch块中处理该异常。最后,无论是否发生异常,finally块都会执行,用于关闭文件流以释放系统资源。
手动抛出异常(throw)
通过 throw 关键字,程序员可以主动抛出自定义的异常或系统内置异常。
if (value < 0) {
throw new IllegalArgumentException("参数值不能为负数");
}
声明方法抛出异常(throws)
在方法签名中,使用 throws 关键字声明方法可能会抛出的异常,将异常处理的责任转移给方法的调用者。
public void readFile(String filePath) throws IOException {
File file = new File(filePath);
//...
}
try-with-resources语句
try-with-resources 是 Java 7 引入的一个特性,用于自动管理资源,特别是那些实现了 AutoCloseable 或 Closeable 接口 的资源,如文件流、数据库连接等。使用 try-with-resources 语句可以确保在 try 代码块执行完毕后,资源能够正确、及时地关闭,即使发生异常也是如此。
示例,假设我们有一个实现了 AutoCloseable 接口的资源类:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) {
// 假设我们有一个需要读取的文件
String filePath = "example.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 在这里,reader 的 close() 方法会在 try 代码块结束时自动被调用
} catch (IOException e) {
e.printStackTrace();
} // 无需在这里显式调用 reader.close()
// 此时的 reader 已经被自动关闭,无需担心资源泄露问题
}
}
在上面的例子中,BufferedReader 是 AutoCloseable 的一个子接口 Closeable 的实现。当 try 代码块执行完毕时,无论是否发生异常,BufferedReader 的 close() 方法都会被自动调用。这样,就无需在 finally 代码块 中显式地关闭资源,从而简化了代码,并减少了忘记关闭资源导致资源泄露的风险。
try-with-resources 语句中的资源声明必须是局部变量,并且这些资源在 try 代码块执行完毕后必须能够被关闭。如果资源不能被关闭(即 close() 方法抛出异常),那么这个异常会被抑制,并且原始的异常(如果有的话)会被重新抛出。如果需要处理 close() 方法抛出的异常,可以使用额外的 try-catch 块来捕获。
自定义异常
除了Java内置的异常类外,我们还可以创建自定义的异常类。这通常用于表示特定于应用程序的错误条件。要创建自定义异常类,需要继承自 Exception类 或其 子类 ,并定义构造函数。
例如:
public class MyCustomException extends Exception {
public MyCustomException(String message) {
super(message);
}
}
然后,可以在需要的地方抛出这个自定义异常:
throw new MyCustomException("自定义异常信息");
Java异常链
异常链 是Java异常处理机制中的一个重要特性,允许在抛出新的异常时,将原始异常作为新异常的“原因”传递。这样做有助于保留原始异常的上下文信息,使得在后续处理中能够更准确地了解异常发生的根本原因。
在Java中,可以通过在构造新的异常时,将原始异常作为参数传递给新异常的构造函数,来创建异常链。这样,新异常就会包含原始异常的引用,从而形成一个链式结构。
简单的示例,演示如何使用Java异常链:
public class ExceptionChainExample {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void method1() throws Exception {
try {
method2();
} catch (Exception e) {
throw new MyCustomException("在method1中捕获到异常", e); // 使用原始异常作为新异常的原因
}
}
public static void method2() throws Exception {
throw new IOException("method2中发生IO异常"); // 假设这里抛出了一个IOException
}
// 自定义异常类
static class MyCustomException extends Exception {
public MyCustomException(String message, Throwable cause) {
super(message, cause);
}
}
}
在这个示例中,method2抛出了一个IOException。当method1捕获到这个异常时,创建了一个新的MyCustomException,并将原始的IOException作为原因传递给了新异常。这样,当在main方法中捕获到MyCustomException时,我们可以通过调用getCause()方法来获取原始的IOException,从而了解异常发生的根本原因。
异常链的好处
异常链 的好处在于它保留了 原始异常 的 堆栈跟踪 信息,使得在调试和排查问题时能够更容易地定位到问题的源头。同时,通过封装原始异常,还可以在 自定义异常 中添加更多的上下文信息,使得异常信息更加丰富和有用。
在处理异常时,建议总是尽量保留并使用 异常链 ,以便在后续的处理中能够充分利用原始异常的信息。
异常处理的注意点
|
注意点 |
描述 |
|
避免空的catch块 |
空的 catch块会 捕获异常但不做任何处理,这会导致程序在出现问题时继续运行,可能会引发更严重的后果。因此,我们应该在catch块中至少记录异常信息或进行适当的处理。 |
|
细化异常处理 |
尽量捕获具体的异常类型,而不是简单地使用 Exception 来捕获所有异常。这样可以更准确地定位问题并进行处理。 |
|
使用finally块释放资源 |
在 finally块 中释放资源是一个很好的习惯,无论是否发生异常,这些资源都会被正确释放。 |
|
合理设计异常结构 |
对于复杂的应用程序,合理设计异常结构可以帮助我们更好地管理异常。可以将相关的异常组织在一起,形成一个继承层次结构。 |
关键术语与概念总结
|
术语 |
描述 |
|
异常(Exception) |
程序执行过程中遇到的问题或错误,如空指针异常(NullPointerException)文件未找到异常(FileNotFoundException)等。 |
|
Checked Exception |
编译时异常,必须在编写代码时 显式处理 ,如果不处理或声明抛出,编译器会报错。例如, IOException 。 |
|
Unchecked Exception |
也称运行时异常,在运行时可能出现的异常,一般由程序错误引起,如数组越界异常(ArrayIndexOutOfBoundsException)、空指针异常(NullPointerException)等。这类异常不必强制处理,但如果在运行时发生且未被捕获,程序会终止执行。 |
|
try catch finally |
try 块 放置可能抛出异常的代码。 catch 块 捕获并处理在 try 块 中抛出的异常。 finally 块 无论是否发生异常,都会被执行的代码块,通常用于资源清理。 |
|
throw 关键字 |
手动抛出一个异常对象。 |
|
throws 关键字 |
在方法签名中声明方法可能抛出的异常,将异常处理的责任转移给调用者。 |
|
异常链(Exception Chaining) |
在一个异常中附加另一个异常,这样可以追踪异常发生的上下文。 |
|
自定义异常 |
通过创建一个新的类继承自 Exception 或其子类,可以定义自己的异常类型。 |
Java内置异常类
Java内置了许多异常类,这些类都是 Throwable类 的直接或间接子类。Throwable类有两个主要的子类: Error 和 Exception 。Error类通常表示严重的问题,这些问题通常是Java 虚拟机 无法或不应该尝试修复的问题,如OutOfMemoryError或StackOverflowError。而 Exception类 及其子类则用于表示程序可以处理的异常情况。
Exception 类及其子类
一些常见的内置异常类及其描述:
输入输出异常
|
异常类 |
描述 |
|
IO Exception |
当应用程序发生输入输出异常时抛出。这是输入输出异常的根类。 |
|
FileNotFound Exception |
当试图打开指定路径名的文件失败时,抛出此异常。 |
|
EOF Exception |
当输入流已经关闭,或者已经到达流的末尾时,抛出此异常。 |
运行时异常
|
异常类 |
描述 |
|
Runtime Exception |
是那些可能在Java虚拟机正常运行期间抛出的异常的超类。编译器不会检查这类异常。 |
|
NullPointer Exception |
当应用程序试图在需要对象的地方使用null时,抛出该异常。 |
|
ArrayIndex OutOfBounds Exception |
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
|
ClassCast Exception |
当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
|
IllegalArgument Exception |
抛出的异常表明向方法传递了一个不合法或不适当的参数。 |
|
NumberFormat Exception |
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
其他常见异常
|
异常类 |
描述 |
|
ClassNotFound Exception |
当应用程序试图加载类,而找不到定义类的.class 文件时,抛出该异常。 |
|
Interrupted Exception |
当线程在等待、睡眠或占用时,另一个线程中断它时,抛出该异常。 |
|
SQL Exception |
提供关于数据库访问错误或其他错误的详细信息。 |
Error 类及其子类
Error 是程序无法处理的严重错误。例如,OutOfMemoryError 表明虚拟机没有更多的内存空间来分配对象,并且垃圾回收器也无法回收更多的空间。
|
异常类 |
描述 |
|
OutOf MemoryError |
当JVM无法为对象分配足够的内存空间时,会抛出这个错误。这通常发生在应用程序试图创建大量对象,而JVM的堆内存不足以容纳这些对象时。 |
|
Stack OverflowError |
当一个方法递归调用过深,或者一个线程请求的栈大小超过了JVM所允许的栈大小时,会抛出这个错误。栈溢出通常意味着程序有逻辑错误,例如无限递归。 |
|
NoClassDef FoundError |
当JVM尝试加载一个类,但没有找到定义该类的.class文件时,会抛出这个错误。这通常发生在类路径设置不正确,或者试图动态加载不存在的类时。 |
|
Virtual MachineError |
通用的错误类型,用于描述虚拟机运行时的严重问题。有两个常见的子类:StackOverflowError和OutOfMemoryError。 |
|
AWTError |
与Java的抽象窗口工具包(AWT)相关的错误。AWT用于创建图形用户界面,如果在这个过程中出现严重问题,就可能抛出这个错误。 |
|
Assertion Error |
当断言(assert)失败时抛出。断言是编程时用于检查某个条件是否为真的语句,如果条件不为真,则抛出AssertionError。 |
|
Thread Death |
表示线程已请求死亡。虽然ThreadDeath是一个Error的子类,但它是唯一一个可以被捕获的Error。通常,应用程序不应该捕获这个错误,除非它们有特殊的处理逻辑。 |
由于 Error类 及其子类通常表示 无法恢复 的 严重问题 ,因此当这些错误发生时,应用程序通常无法继续正常运行。在编写Java程序时,虽然不需要显式地处理这些错误,但了解它们以及它们可能的原因仍