Java 异常处理 (Exception Handling) 核心知识点
Java 异常处理 (Exception Handling) 核心知识点
1. 什么是异常?
在 Java 中,异常 (Exception) 是指在程序运行期间发生的、中断了正常指令流的非正常事件。例如,尝试除以零、访问数组的越界索引、打开一个不存在的文件等。
Java 的异常处理机制提供了一种结构化的方式来捕获和处理这些错误,从而使程序能够更健壮、更可靠地运行,而不是直接崩溃。
2. 异常的层次结构
Java 中所有异常的根父类是 java.lang.Throwable。它有两个主要的子类:Error 和 Exception。
java.lang.Throwable
/ \
/ \
Error Exception
/ \ / \
... ... / \
RuntimeException IOException
(Unchecked) (Checked)
/ \
... ...
2.1 Error (错误)
- 定义:表示严重的问题,通常是 JVM 内部或底层资源相关的、无法恢复的错误。应用程序不应该也无法处理这些错误。
- 例子:
OutOfMemoryError:内存溢出。StackOverflowError:栈溢出,通常由无限递归引起。NoClassDefFoundError:JVM在加载一个类时,在编译时该类是可用的,但在运行时找不到了。
- 特点:
Error和它的子类都是非受检异常 (Unchecked),编译器不强制你处理它们。
2.2 Exception (异常)
- 定义:表示程序本身可以处理的异常情况。这是我们日常编程中主要关注和处理的部分。
Exception又分为两大类:受检异常 (Checked Exception) 和 非受检异常 (Unchecked Exception)。
3. 受检异常 vs. 非受检异常
Java 中的异常分为两大类:受检异常 (Checked Exception) 和非受检异常 (Unchecked Exception),它们在处理机制、设计哲学和使用场景上有所不同。
| 特性 (Feature) | 受检异常 (Checked Exception) | 非受检异常 (Unchecked Exception) |
|---|---|---|
| 编译时检查 (Compile-time Check) | 编译器强制处理。不处理会导致编译错误。 | 编译器不强制处理。不处理也不会导致编译错误。 |
| 继承关系 (Inheritance) | 直接继承自 Exception (但不包括 RuntimeException 及其子类)。 |
继承自 RuntimeException 或 Error。 |
| 使用场景 (Usage Scenarios) | 通常用于表示可预见但无法完全避免的外部问题,需要调用者显式处理以保证程序的健壮性。例如:IOException, SQLException, ClassNotFoundException 等。 |
通常用于表示程序逻辑错误或编程缺陷,理论上可以通过修改代码来避免。例如:NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException 等。 |
3.1 受检异常 (Checked Exception)
- 定义:在编译时就必须被显式处理的异常。编译器会检查你的代码,如果你调用的方法声明了会抛出受检异常,你必须二选一:
- 使用
try-catch块捕获并处理它。 - 在当前方法签名中使用
throws关键字声明,将异常抛给上层调用者处理。
- 使用
- 特点:通常表示那些在程序正常运行过程中可预见但又无法完全避免的问题,例如外部资源或环境相关的问题。
- 例子:
IOException:读写文件时发生错误。SQLException:数据库操作发生错误。ClassNotFoundException:通过Class.forName()加载类但找不到。
3.2 非受检异常 (Unchecked Exception)
- 定义:也称为运行时异常 (RuntimeException)。
RuntimeException类及其所有子类都属于非受检异常。编译器不强制你处理它们。 - 特点:通常表示程序中的逻辑错误或编程缺陷。这些异常理论上是可以通过修正代码来避免的。
- 例子:
NullPointerException:在一个null引用上调用方法或访问字段。ArrayIndexOutOfBoundsException:数组索引越界。IllegalArgumentException:向方法传递了不合法的参数。ClassCastException:对象类型转换失败。
4. 核心处理机制:try-catch-finally
这是 Java 异常处理的核心语法结构。
try {
// 可能会抛出异常的代码块
} catch (ExceptionType1 e1) {
// 捕获并处理类型为 ExceptionType1 的异常
} catch (ExceptionType2 e2) {
// 捕获并处理类型为 ExceptionType2 的异常
} finally {
// 无论是否发生异常,都必定会执行的代码块
}
try:包裹可能会产生异常的代码。catch:用于捕获并处理在try块中抛出的特定类型的异常。可以有多个catch块来处理不同类型的异常,子类异常必须在父类异常之前捕获。finally:提供一个“必定执行”的代码块。
5. finally 块详解 (高频面试题)
5.1 作用和特点
finally 块最主要的作用是保证资源能够被正确释放。无论 try 块中的代码是正常执行完毕,还是因为发生异常而中途退出,finally 块中的代码几乎总会被执行。
典型应用:关闭文件流、数据库连接、网络连接、释放锁等。
Connection conn = null;
try {
conn = openConnection();
// ... 执行数据库操作 ...
} catch (SQLException e) {
// ... 处理异常 ...
} finally {
if (conn != null) {
try {
conn.close(); // 确保数据库连接被关闭
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5.2 finally 块不执行的特殊情况
尽管 finally 的执行得到了强有力的保证,但在以下几种极端情况下,它也不会被执行:
- 在
try或catch块中调用了System.exit():这个方法会直接终止当前正在运行的 Java 虚拟机,所有后续代码(包括finally)都不会有执行的机会。 - JVM 崩溃或关机:如果 JVM 因为操作系统错误、硬件故障或断电等外部原因而崩溃,
finally块自然无法执行。 - 守护线程:如果执行
try-catch-finally的线程是一个守护线程(Daemon Thread),并且此时所有非守护线程都已经结束,那么 JVM 会直接退出,守护线程中的finally块可能不会被执行。
5.3 finally 与 return 的交互
这是一个非常经典的面试陷阱:
- 规则:
finally块在try或catch块中的return语句执行之前执行。 - 情况一:
finally块没有return
try {
return 1; // 1. 准备返回 1
} finally {
System.out.println("finally block"); // 2. 执行 finally
}
// 3. 真正返回 1
* **情况二:`finally` 块中也有 `return`**
```java
try {
return 1; // 准备返回 1,但这个返回值会被忽略
} finally {
return 2; // finally 中的 return 会覆盖 try/catch 中的 return
}
**结论**:`finally` 块中的 `return` 会“覆盖”`try` 或 `catch` 块中的 `return`。这是一个非常不好的编程实践,**应该极力避免在 `finally` 块中使用 `return`**。
6. throw vs. throws 关键字
-
throw:- 位置:用在方法体内部。
- 作用:手动抛出一个异常对象。执行到
throw时,方法立即终止,并将异常抛出。 - 示例:
throw new RuntimeException("参数不能为空");
-
throws:- 位置:用在方法签名上,在参数列表之后。
- 作用:声明这个方法可能会抛出某种类型的受检异常 (Checked Exception)。它告诉调用者:“调用我这个方法,你需要处理我可能抛出的这些异常。”
- 示例:
public void readFile() throws IOException { ... }
7. try-with-resources (Java 7+ 推荐)
这是 Java 7 引入的一个语法糖,用于自动管理实现了 AutoCloseable 或 Closeable 接口的资源,极大地简化了代码并避免了资源泄漏。
传统方式(使用 finally):
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("file.txt"));
return br.readLine();
} finally {
if (br != null) {
br.close(); // 必须在 finally 中手动关闭
}
}
try-with-resources 方式:
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
return br.readLine();
} // br 会在这里被自动关闭,无论是否发生异常
优点:
- 代码更简洁:无需编写
finally块和繁琐的close()调用。 - 更安全:绝对不会忘记关闭资源,从根本上杜绝了资源泄漏的可能。
强烈推荐在处理文件流、数据库连接等需要关闭的资源时,使用 try-with-resources。
8. final, finally, finalize() 对比 (高频面试题)
这三个词在 Java 中虽然名字相似,但它们代表的概念和用途完全不同,是面试中常用来考察基础知识的考点。
8.1 final 关键字
- 类型:关键字 (Keyword),一个修饰符。
- 作用:核心作用是“禁止改变”。
- 修饰变量:值在初始化后不能被修改。对于引用类型,是指引用本身不能再指向其他对象,但对象内部状态可变。
- 修饰方法:方法不能被子类重写 (Override)。
- 修饰类:类不能被继承。
- 使用场景:定义常量、创建不可变对象、防止方法被恶意重写、实现某些设计模式(如模板方法模式)。
8.2 finally 代码块
- 类型:异常处理结构 (
try-catch-finally) 中的一个代码块 (Code Block)。 - 作用:核心作用是“保证执行”。无论
try块中的代码是正常结束,还是因为异常中断,finally块中的代码几乎总会被执行。 - 使用场景:主要用于资源释放,例如关闭文件流、数据库连接、网络连接、释放锁等,确保这些关键清理操作在任何情况下都能得到执行,避免资源泄漏。
8.3 finalize() 方法
- 类型:
java.lang.Object类中的一个方法 (Method)。 - 作用:设计初衷是在垃圾回收器 (GC) 回收一个对象之前,给该对象一个“最后的机会”来执行一些清理操作(例如释放操作系统级别的本地资源)。
- 使用场景:强烈不推荐使用。它存在诸多问题,已在 Java 9 中被废弃。
- 执行不确定:不保证一定会被调用,也不保证何时被调用。
- 性能影响:会拖慢垃圾回收的速度。
- 错误处理复杂:
finalize()中抛出的异常会被忽略。
- 现代替代方案:应使用
try-with-resources语句或finally块来管理和释放资源。
8.4 总结对比表格
| 特性 (Feature) | final |
finally |
finalize() |
|---|---|---|---|
| 类型 (Type) | 关键字 (Keyword) | 代码块 (Code Block) | 方法 (Method) |
| 作用 (Purpose) | 修饰类、方法、变量,使其不可变/不可继承/不可重写 | 异常处理的一部分,用于资源清理,保证代码执行 | GC回收前的“最后遗言”,用于清理资源 (已废弃) |
| 使用场景 (Usage) | 定义常量、工具类、防止方法被重写 | try-catch 结构中,关闭文件流、数据库连接等 |
不推荐使用。旧代码中可能用于清理本地资源。 |
| 执行时机 | 编译期检查,影响运行时行为 | try-catch 块结束时,无论有无异常 |
由垃圾回收器 (GC) 在回收对象前不确定地调用 |
| 当前状态 | Java 核心关键字 | Java 核心异常处理机制 | 自 Java 9 起已废弃 (Deprecated) |

浙公网安备 33010602011771号