引言:什么是异常?
异常(Exception)是程序在运行过程中发生的错误事件。Java的异常处理机制让我们能够捕获这些错误并做出适当的处理,而不是让程序崩溃。
异常类比
异常就像生活中的意外情况:
正常流程:出门 → 上班 → 完成工作
异常情况:出门 → 遇到堵车(异常)→ 绕道走 → 继续上班
如果没有异常处理:
出门 → 遇到堵车 → 程序崩溃
有异常处理:
出门 → 遇到堵车(捕获异常)→ 绕道 → 继续上班
异常的分类
| 类型 | 说明 | 是否需要处理 |
|---|---|---|
| Error | 程序无法处理的严重错误,如内存溢出 | 不需要(也无法处理) |
| RuntimeException | 运行时异常,如空指针、数组越界 | 可以选择不处理(但最好处理) |
| 受检查异常 | 必须处理的异常,如IOException | 必须处理 |
常见的运行时异常
- NullPointerException:空指针异常,调用null对象的方法
- ArrayIndexOutOfBoundsException:数组越界
- ArithmeticException:算术异常,如除以0
- ClassCastException:类型转换异常
- IllegalArgumentException:非法参数异常
try-catch-finally
基本语法
try {
// 可能发生异常的代码
} catch (异常类型1 变量名) {
// 处理异常类型1
} catch (异常类型2 变量名) {
// 处理异常类型2
} finally {
// 无论是否发生异常都会执行的代码
}
示例:捕获除零异常
public class ExceptionDemo {
public static void main(String[] args) {
int[] numbers = {10, 20, 30};
int index = 3;
try {
System.out.println("尝试访问数组...");
int result = numbers[index] / 0;
System.out.println("不会执行到这里");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到数组越界异常: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("捕获到算术异常: " + e.getMessage());
} finally {
System.out.println("finally代码块总是执行");
}
System.out.println("程序继续执行...");
}
}
执行流程:
- 执行try块中的代码
- 如果发生异常,停止执行try块,立即跳到catch匹配
- 执行catch块中的处理代码
- 最后执行finally块中的代码
catch的顺序
异常捕获必须从小到大(子类在前,父类在后):
try {
// 可能发生多种异常的代码
} catch (NullPointerException e) {
// 先捕获具体的异常
} catch (Exception e) {
// 再捕获更通用的异常
}
printStackTrace() vs getMessage()
try {
String str = null;
System.out.println(str.length()); // 触发NullPointerException
} catch (NullPointerException e) {
// 打印完整的异常堆栈信息(包含方法调用路径)
e.printStackTrace();
// 仅打印异常信息
System.out.println("异常信息: " + e.getMessage());
// 获取异常类型名称
System.out.println("异常类型: " + e.getClass().getName());
}
throw与throws
throw - 抛出异常
throw用于在代码中主动抛出异常。
public class ThrowDemo {
public static void main(String[] args) {
int age = -5;
if (age < 0) {
// 主动抛出异常
throw new IllegalArgumentException("年龄不能为负数: " + age);
}
System.out.println("年龄: " + age);
}
}
throws - 声明异常
throws用于在方法声明时声明可能抛出的异常。
import java.io.FileReader;
import java.io.IOException;
public class ThrowsDemo {
// 声明此方法可能抛出IOException
public static void readFile() throws IOException {
FileReader reader = new FileReader("test.txt");
reader.read();
reader.close();
}
public static void main(String[] args) {
try {
readFile();
} catch (IOException e) {
System.out.println("文件读取失败: " + e.getMessage());
}
}
}
throw vs throws:
- throw:在方法内部使用,后面跟一个异常对象
- throws:在方法声明处使用,后面跟异常类型(多个用逗号分隔)
自定义异常
创建自定义异常
public class AgeException extends Exception {
public AgeException() {
super();
}
public AgeException(String message) {
super(message);
}
}
使用自定义异常
public class Person {
private int age;
public void setAge(int age) throws AgeException {
if (age < 0 || age > 150) {
throw new AgeException("年龄不合法: " + age);
}
this.age = age;
}
public int getAge() {
return age;
}
}
// 使用
Person person = new Person();
try {
person.setAge(-5);
} catch (AgeException e) {
System.out.println("捕获自定义异常: " + e.getMessage());
}
异常链
异常链允许我们保存原始异常的信息,同时添加新的上下文。
try {
// 原始操作,可能抛出异常
int[] arr = {1, 2, 3};
int result = arr[10];
} catch (ArrayIndexOutOfBoundsException e) {
// 包装成新的异常,保留原始异常
throw new RuntimeException("获取数据失败", e);
}
最佳实践
捕获具体异常:尽量捕获具体异常类型,而不是Exception
不要吞掉异常:不要只写catch空块,至少要记录日志
关闭资源:在finally中关闭资源,或使用try-with-resources
不要用异常做流程控制:异常用于处理异常情况,不要用于正常流程
小结
- 异常是程序运行时的错误事件
- try-catch-finally:基本异常处理结构
- throw:主动抛出异常;throws:声明异常
- 自定义异常:继承Exception或RuntimeException
- 异常捕获要从小到大(子类在前)
- 使用try-with-resources自动关闭资源
☕ Java 在线代码编辑器
📝 运行结果