Appearance
15.1 什么是异常?
异常的概念
异常(Exception)是指程序运行过程中出现的非正常情况,这些情况会中断程序的正常执行流程。在 Java 中,异常是一种对象,它表示程序运行时发生的错误或异常情况。
异常的分类
Java 中的异常分为两大类:
- Checked Exception(检查型异常):编译器要求必须处理的异常,必须通过
try-catch捕获或通过throws声明 - Unchecked Exception(非检查型异常):编译器不要求必须处理的异常,包括
RuntimeException及其子类
异常的层次结构
Java 中的异常层次结构如下:
- Throwable:所有异常和错误的父类
- Error:表示严重的错误,通常程序无法恢复,如
OutOfMemoryError、StackOverflowError - Exception:表示可以被捕获和处理的异常
- RuntimeException:运行时异常,属于非检查型异常
- 其他异常:检查型异常,如
IOException、SQLException
- Error:表示严重的错误,通常程序无法恢复,如
异常的产生
异常的产生有两种方式:
- 自动产生:当程序运行过程中遇到错误时,JVM 会自动创建并抛出异常
- 手动抛出:使用
throw关键字手动抛出异常
异常的处理
异常的处理方式有两种:
- 捕获异常:使用
try-catch语句捕获并处理异常 - 声明异常:使用
throws关键字声明方法可能抛出的异常,由调用者处理
异常的特点
- 异常是对象:异常是
Throwable类或其子类的实例 - 异常会中断程序执行:当异常发生时,程序会跳转到异常处理代码
- 异常可以传递:异常可以从方法传递到调用者
- 异常可以被捕获和处理:使用
try-catch语句捕获和处理异常
异常与错误的区别
| 特性 | 异常(Exception) | 错误(Error) |
|---|---|---|
| 类型 | 可恢复的问题 | 严重的问题 |
| 处理方式 | 可以捕获和处理 | 通常无法处理 |
| 示例 | IOException、NullPointerException | OutOfMemoryError、StackOverflowError |
| 继承关系 | 继承自 Exception | 继承自 Error |
异常的作用
- 分离错误处理代码:将正常业务逻辑与错误处理逻辑分离
- 提高程序的健壮性:即使出现异常,程序也能继续执行
- 提供错误信息:异常对象包含错误信息,有助于调试
- 简化错误处理:使用统一的异常处理机制,减少代码重复
示例:异常的产生和处理
示例 1:自动产生异常
java
public class AutoExceptionExample {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // 数组索引越界,自动产生 ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("发生异常: " + e.getMessage());
}
}
}示例 2:手动抛出异常
java
public class ManualExceptionExample {
public static void main(String[] args) {
try {
int age = -10;
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数"); // 手动抛出异常
}
System.out.println("年龄: " + age);
} catch (IllegalArgumentException e) {
System.out.println("发生异常: " + e.getMessage());
}
}
}示例 3:异常的传递
java
public class ExceptionPropagationExample {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("在 main 方法中捕获异常: " + e.getMessage());
}
}
public static void method1() throws Exception {
method2();
}
public static void method2() throws Exception {
throw new Exception("在 method2 中抛出异常");
}
}常见的异常类型
运行时异常(非检查型异常)
- NullPointerException:空指针异常,当尝试访问 null 对象的成员时抛出
- ArrayIndexOutOfBoundsException:数组索引越界异常,当访问数组的无效索引时抛出
- ArithmeticException:算术异常,如除以零
- IllegalArgumentException:非法参数异常,当方法接收到无效参数时抛出
- ClassCastException:类型转换异常,当尝试将对象转换为不兼容的类型时抛出
- NumberFormatException:数字格式异常,当尝试将字符串转换为数字失败时抛出
检查型异常
- IOException:输入/输出异常,如文件读写错误
- SQLException:数据库操作异常
- ClassNotFoundException:类未找到异常
- InterruptedException:线程中断异常
- FileNotFoundException:文件未找到异常
异常处理的最佳实践
捕获具体的异常:尽量捕获具体的异常类型,而不是捕获所有异常
不要捕获所有异常:避免使用
catch (Exception e)捕获所有异常处理异常:捕获异常后应该进行适当的处理,而不是简单地打印错误信息
使用 finally 块:对于需要释放的资源,使用 finally 块确保资源被释放
抛出有意义的异常:手动抛出异常时,提供有意义的错误信息
不要忽略异常:不要捕获异常后不做任何处理
使用 try-with-resources:对于实现了 AutoCloseable 接口的资源,使用 try-with-resources 自动关闭
示例:异常处理的最佳实践
示例 1:捕获具体的异常
java
public class SpecificExceptionExample {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组索引越界: " + e.getMessage());
} catch (Exception e) {
System.out.println("其他异常: " + e.getMessage());
}
}
}示例 2:使用 finally 块
java
import java.io.FileInputStream;
import java.io.IOException;
public class FinallyExample {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("example.txt");
// 读取文件
} catch (IOException e) {
System.out.println("文件操作异常: " + e.getMessage());
} finally {
// 确保资源被释放
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
System.out.println("关闭文件异常: " + e.getMessage());
}
}
}
}
}示例 3:使用 try-with-resources
java
import java.io.FileInputStream;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("example.txt")) {
// 读取文件
} catch (IOException e) {
System.out.println("文件操作异常: " + e.getMessage());
}
// 资源自动关闭,不需要 finally 块
}
}异常处理的常见误区
1. 过度使用异常
症状:使用异常来控制正常的程序流程
解决方案:只在真正的异常情况下使用异常,不要使用异常来控制正常的程序流程
示例:
java
// 错误:使用异常控制正常流程
public int getValue(int index) {
try {
return array[index];
} catch (ArrayIndexOutOfBoundsException e) {
return -1;
}
}
// 正确:使用条件检查
public int getValue(int index) {
if (index >= 0 && index < array.length) {
return array[index];
} else {
return -1;
}
}2. 捕获异常后不处理
症状:捕获异常后不做任何处理,只是打印错误信息
解决方案:捕获异常后应该进行适当的处理,如恢复程序状态、记录日志、通知用户等
示例:
java
// 错误:捕获异常后不处理
try {
// 可能抛出异常的代码
} catch (Exception e) {
e.printStackTrace(); // 只是打印错误信息
}
// 正确:捕获异常后进行处理
try {
// 可能抛出异常的代码
} catch (Exception e) {
// 记录日志
logger.error("发生异常", e);
// 恢复程序状态或通知用户
System.out.println("操作失败,请重试");
}3. 抛出异常时不提供详细信息
症状:抛出异常时不提供详细的错误信息
解决方案:抛出异常时提供详细的错误信息,有助于调试和错误处理
示例:
java
// 错误:抛出异常时不提供详细信息
if (age < 0) {
throw new IllegalArgumentException();
}
// 正确:抛出异常时提供详细信息
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数,当前值: " + age);
}4. 捕获异常后重新抛出
症状:捕获异常后立即重新抛出,没有进行任何处理
解决方案:如果不需要处理异常,应该使用 throws 声明而不是捕获后重新抛出
示例:
java
// 错误:捕获后立即重新抛出
public void method() {
try {
// 可能抛出异常的代码
} catch (Exception e) {
throw e; // 没有进行任何处理
}
}
// 正确:使用 throws 声明
public void method() throws Exception {
// 可能抛出异常的代码
}总结
异常是 Java 中处理运行时错误的机制,它可以帮助我们:
- 分离错误处理代码:将正常业务逻辑与错误处理逻辑分离
- 提高程序的健壮性:即使出现异常,程序也能继续执行
- 提供错误信息:异常对象包含错误信息,有助于调试
- 简化错误处理:使用统一的异常处理机制,减少代码重复
Java 中的异常分为两大类:
- Checked Exception:编译器要求必须处理的异常
- Unchecked Exception:编译器不要求必须处理的异常
异常的处理方式有两种:
- 捕获异常:使用
try-catch语句捕获并处理异常 - 声明异常:使用
throws关键字声明方法可能抛出的异常
通过合理使用异常处理机制,可以提高代码的可读性、可维护性和健壮性。
