在 Java 开发中,"异常" 是绕不开的话题。它像一把双刃剑:既可能让程序崩溃,也能帮我们精准定位问题。本文将从异常的体系、分类、处理方式到实战技巧,带你全面掌握 Java 异常处理。

一、异常的概述
异常是指程序运行过程中发生的非正常事件,它会打断程序的正常执行流程。比如文件不存在、网络连接中断、数组越界等,都属于异常场景。
Java 通过异常机制来统一处理这些意外情况,让程序在出错时能有 "补救" 或 "优雅退出" 的机会。
二、异常的体系:Throwable 是一切的开始
Java 的异常体系以java.lang.Throwable为根类,它有两个直接子类:Error和Exception。
1. Error(错误)
- 属于系统级问题,开发者几乎无法处理。
- 示例:
OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出)。 - 程序遇到 Error 时,通常会直接崩溃,无需也无法捕获。
// 演示栈溢出Error
public class StackOverflowDemo {
public static void main(String[] args) {
stackOverflowMethod();
}
public static void stackOverflowMethod() {
stackOverflowMethod(); // 无限递归,导致StackOverflowError
}
}
2. Exception(异常)
- 属于业务或逻辑级问题,开发者可以通过代码处理。
- 又分为三类:编译期异常、运行时异常、自定义异常。
(1)编译期异常(Checked Exception)
- 编译器强制要求处理的异常(不处理则编译报错)。
- 示例:
IOException(文件读写异常)、SQLException(数据库操作异常)。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class CheckedExceptionDemo {
public static void main(String[] args) {
// 编译报错:未处理FileNotFoundException
// FileInputStream fis = new FileInputStream("test.txt");
// 正确处理方式1:try-catch捕获
try {
FileInputStream fis = new FileInputStream("test.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 正确处理方式2:throws向上抛出
// public static void main(String[] args) throws FileNotFoundException {
// FileInputStream fis = new FileInputStream("test.txt");
// }
}
}
(2)运行时异常(RuntimeException,Unchecked Exception)
- 编译器不强制处理,可在运行时才暴露的异常。
- 示例:
NullPointerException(空指针)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换)。
(3)自定义异常
- 开发者根据业务需求自定义的异常类,需继承
Exception或RuntimeException。 - 场景:用户登录失败、参数校验不通过等业务异常。
// 自定义编译期异常
class LoginFailedException extends Exception {
public LoginFailedException(String message) {
super(message);
}
}
// 自定义运行时异常
class ParamInvalidException extends RuntimeException {
public ParamInvalidException(String message) {
super(message);
}
}
public class CustomExceptionDemo {
public static void login(String username, String password) throws LoginFailedException {
if (!"admin".equals(username) || !"123456".equals(password)) {
throw new LoginFailedException("用户名或密码错误");
}
}
public static void checkParam(String param) {
if (param == null || param.isEmpty()) {
throw new ParamInvalidException("参数不能为空");
}
}
public static void main(String[] args) {
try {
login("user", "123");
} catch (LoginFailedException e) {
System.out.println("登录失败:" + e.getMessage());
}
checkParam(null); // 运行时抛出ParamInvalidException
}
}
三、异常的处理方式:三种策略 + 关键字组合
Java 提供了多种异常处理方式,核心是抛出和捕获。
1. 向上抛出(throws + throw)
- throw:在方法内手动抛出一个异常对象。
- throws:在方法声明处声明该方法可能抛出的异常,将异常 "甩给" 调用者处理。
public class ThrowDemo {
// 声明可能抛出的异常
public static void readFile(String path) throws FileNotFoundException {
if (!"valid.txt".equals(path)) {
// 手动抛出异常
throw new FileNotFoundException("文件不存在");
}
}
public static void main(String[] args) {
try {
readFile("invalid.txt");
} catch (FileNotFoundException e) {
System.out.println("捕获到异常:" + e.getMessage());
}
}
}
2. try-catch:捕获并处理异常
- try 块:包裹可能抛出异常的代码。
- catch 块:捕获指定类型的异常并处理,可多个 catch 按异常从小到大排列。
public class TryCatchDemo {
public static void main(String[] args) {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // 数组越界
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到数组越界异常");
e.printStackTrace();
} catch (Exception e) { // 父类异常需放在后面
System.out.println("捕获到其他异常");
}
}
}
3. try-catch-finally:捕获 + 最终执行
- finally 块:无论是否发生异常,都会执行的代码(常用来释放资源,如关闭流、连接)。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class TryCatchFinallyDemo {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
System.out.println("读取文件中...");
} catch (FileNotFoundException e) {
System.out.println("文件未找到");
} finally {
// 最终执行:关闭资源
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("finally块执行完毕");
}
}
}
4. try-finally:仅捕获最终执行(不处理异常)
- 场景:需要确保资源释放,但不关心异常本身(异常会继续向上抛)。
public class TryFinallyDemo {
public static void main(String[] args) {
try {
int result = 10 / 0; // 运行时异常
} finally {
System.out.println("无论是否异常,这里都会执行");
}
}
}
四、面试高频:final 和 finally 的区别
这是 Java 面试的经典问题,核心区别如下:
| 对比项 | final | finally |
|---|---|---|
| 功能 | 修饰类、方法、变量,表 "不可变" | 异常处理的关键字,表 "最终执行" |
| 场景 | 类不可继承、方法不可重写、变量不可修改 | 用于 try-catch 块后,确保代码执行 |
| 关联 | 与异常处理无直接关联 | 仅在异常处理中使用 |
// final示例
final class FinalClass {} // 不可继承
final void finalMethod() {} // 不可重写
final int FINAL_VAR = 10; // 不可修改
// finally示例
try {
// 可能抛异常的代码
} finally {
// 最终执行的代码
}
五、异常处理的最佳实践
- 明确区分异常类型:运行时异常(业务逻辑)和编译期异常(外部依赖)分开处理。
- 避免空 catch 块:捕获异常后至少打印日志或做降级处理,否则异常会被 "吃掉"。
- 优先捕获具体异常:多个 catch 块时,子类异常在前,父类异常在后。
- 资源释放用 finally:涉及 IO、网络连接等资源,务必在 finally 中关闭。
- 自定义异常见名知意:业务异常命名要清晰,如
OrderNotFoundException。
总结
Java 异常体系是保障程序健壮性的基石:
- 理解
Error和Exception的区别,聚焦于可处理的Exception。 - 灵活运用抛出和捕获策略,结合
try-catch-finally关键字组合。 - 遵循最佳实践,让异常从 "问题" 变成 "调试助手"。
掌握这些知识,你就能在程序出错时从容应对,写出更健壮的 Java 代码!
1200

被折叠的 条评论
为什么被折叠?



