Appearance
15.3 常见异常类型
异常的分类
在 Java 中,异常主要分为两大类:
- 运行时异常(非检查型异常):继承自
RuntimeException,编译器不要求必须处理 - 检查型异常:直接继承自
Exception,编译器要求必须处理
运行时异常(非检查型异常)
1. NullPointerException(空指针异常)
产生原因:当尝试访问 null 对象的成员(属性或方法)时抛出
常见场景:
- 调用 null 对象的方法
- 访问 null 对象的属性
- 长度为 null 的数组的长度
- 抛 null 作为异常
示例:
java
public class NullPointerExceptionExample {
public static void main(String[] args) {
String str = null;
try {
System.out.println(str.length()); // 抛出 NullPointerException
} catch (NullPointerException e) {
System.out.println("空指针异常: " + e.getMessage());
}
}
}解决方案:
- 在使用对象前检查是否为 null
- 使用 Optional 类处理可能为 null 的情况
- 避免返回 null,使用空集合或空对象代替
2. ArrayIndexOutOfBoundsException(数组索引越界异常)
产生原因:当访问数组的无效索引时抛出,索引小于 0 或大于等于数组长度
常见场景:
- 访问负数索引
- 访问大于或等于数组长度的索引
示例:
java
public class ArrayIndexOutOfBoundsExceptionExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3};
try {
System.out.println(numbers[5]); // 抛出 ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组索引越界异常: " + e.getMessage());
}
}
}解决方案:
- 在访问数组前检查索引是否在有效范围内
- 使用循环时,确保循环变量在有效范围内
3. ArithmeticException(算术异常)
产生原因:当发生算术错误时抛出,如除以零
常见场景:
- 整数除以零
- 取模运算时除数为零
示例:
java
public class ArithmeticExceptionExample {
public static void main(String[] args) {
try {
int result = 10 / 0; // 抛出 ArithmeticException
} catch (ArithmeticException e) {
System.out.println("算术异常: " + e.getMessage());
}
}
}解决方案:
- 在进行除法或取模运算前检查除数是否为零
- 使用 try-catch 捕获可能的算术异常
4. IllegalArgumentException(非法参数异常)
产生原因:当方法接收到无效参数时抛出
常见场景:
- 传递负数给要求正数的方法
- 传递 null 给不接受 null 的方法
- 传递不符合要求的参数值
示例:
java
public class IllegalArgumentExceptionExample {
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());
}
}
}解决方案:
- 在方法中检查参数的有效性
- 提供清晰的参数要求文档
- 使用 IllegalArgumentException 明确指示参数错误
5. ClassCastException(类型转换异常)
产生原因:当尝试将对象转换为不兼容的类型时抛出
常见场景:
- 将父类对象强制转换为子类类型
- 将接口实现类对象转换为不相关的类型
示例:
java
public class ClassCastExceptionExample {
public static void main(String[] args) {
Object obj = "Hello";
try {
Integer number = (Integer) obj; // 抛出 ClassCastException
} catch (ClassCastException e) {
System.out.println("类型转换异常: " + e.getMessage());
}
}
}解决方案:
- 在类型转换前使用 instanceof 检查类型兼容性
- 避免不必要的类型转换
- 使用泛型减少类型转换
6. NumberFormatException(数字格式异常)
产生原因:当尝试将字符串转换为数字失败时抛出
常见场景:
- 将包含非数字字符的字符串转换为数字
- 将空字符串转换为数字
- 将超过数字类型范围的字符串转换为数字
示例:
java
public class NumberFormatExceptionExample {
public static void main(String[] args) {
try {
String str = "abc";
int number = Integer.parseInt(str); // 抛出 NumberFormatException
} catch (NumberFormatException e) {
System.out.println("数字格式异常: " + e.getMessage());
}
}
}解决方案:
- 在转换前检查字符串是否为有效的数字格式
- 使用 try-catch 捕获可能的数字格式异常
- 使用正则表达式验证输入字符串
7. IndexOutOfBoundsException(索引越界异常)
产生原因:当访问集合或字符串的无效索引时抛出
常见场景:
- 访问 List 的无效索引
- 访问 String 的无效索引
示例:
java
public class IndexOutOfBoundsExceptionExample {
public static void main(String[] args) {
String str = "Hello";
try {
char c = str.charAt(10); // 抛出 StringIndexOutOfBoundsException
} catch (IndexOutOfBoundsException e) {
System.out.println("索引越界异常: " + e.getMessage());
}
}
}解决方案:
- 在访问集合或字符串前检查索引是否在有效范围内
- 使用循环时,确保循环变量在有效范围内
8. NoSuchElementException(无元素异常)
产生原因:当尝试访问不存在的元素时抛出
常见场景:
- 迭代器到达末尾后继续调用 next()
- 从空集合中获取元素
示例:
java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class NoSuchElementExceptionExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
try {
iterator.next(); // 抛出 NoSuchElementException
} catch (java.util.NoSuchElementException e) {
System.out.println("无元素异常: " + e.getMessage());
}
}
}解决方案:
- 在调用 next() 前使用 hasNext() 检查
- 在从集合中获取元素前检查集合是否为空
检查型异常
1. IOException(输入/输出异常)
产生原因:当发生输入/输出错误时抛出
常见场景:
- 文件读写错误
- 网络连接错误
- 流操作错误
示例:
java
import java.io.FileInputStream;
import java.io.IOException;
public class IOExceptionExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("example.txt")) {
// 读取文件
} catch (IOException e) {
System.out.println("输入/输出异常: " + e.getMessage());
}
}
}解决方案:
- 使用 try-catch 捕获 IOException
- 确保资源正确关闭
- 检查文件路径和权限
2. FileNotFoundException(文件未找到异常)
产生原因:当尝试打开不存在的文件时抛出
常见场景:
- 打开不存在的文件
- 打开路径错误的文件
示例:
java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileNotFoundExceptionExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("non_existent_file.txt")) {
// 读取文件
} catch (FileNotFoundException e) {
System.out.println("文件未找到异常: " + e.getMessage());
} catch (IOException e) {
System.out.println("输入/输出异常: " + e.getMessage());
}
}
}解决方案:
- 在打开文件前检查文件是否存在
- 使用 try-catch 捕获 FileNotFoundException
- 提供正确的文件路径
3. SQLException(数据库操作异常)
产生原因:当发生数据库操作错误时抛出
常见场景:
- 数据库连接错误
- SQL 语句语法错误
- 数据库权限错误
示例:
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SQLExceptionExample {
public static void main(String[] args) {
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
// 执行数据库操作
} catch (SQLException e) {
System.out.println("数据库操作异常: " + e.getMessage());
}
}
}解决方案:
- 使用 try-catch 捕获 SQLException
- 确保数据库连接正确配置
- 检查 SQL 语句语法
4. ClassNotFoundException(类未找到异常)
产生原因:当尝试加载不存在的类时抛出
常见场景:
- 使用 Class.forName() 加载不存在的类
- 类路径配置错误
- 缺少依赖包
示例:
java
public class ClassNotFoundExceptionExample {
public static void main(String[] args) {
try {
Class.forName("com.example.NonExistentClass"); // 抛出 ClassNotFoundException
} catch (ClassNotFoundException e) {
System.out.println("类未找到异常: " + e.getMessage());
}
}
}解决方案:
- 确保类名正确
- 检查类路径配置
- 确保依赖包已添加
5. InterruptedException(线程中断异常)
产生原因:当线程在等待、睡眠或被占用时被中断时抛出
常见场景:
- 线程 sleep() 时被中断
- 线程 wait() 时被中断
- 线程 join() 时被中断
示例:
java
public class InterruptedExceptionExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程中断异常: " + e.getMessage());
}
});
thread.start();
thread.interrupt(); // 中断线程
}
}解决方案:
- 使用 try-catch 捕获 InterruptedException
- 在捕获异常后恢复中断状态
- 合理处理线程中断
错误(Error)
1. OutOfMemoryError(内存不足错误)
产生原因:当 JVM 内存不足时抛出
常见场景:
- 创建过多对象
- 内存泄漏
- 大对象分配
示例:
java
public class OutOfMemoryErrorExample {
public static void main(String[] args) {
try {
List<Object> list = new ArrayList<>();
while (true) {
list.add(new Object());
}
} catch (OutOfMemoryError e) {
System.out.println("内存不足错误: " + e.getMessage());
}
}
}解决方案:
- 增加 JVM 内存
- 优化内存使用
- 避免内存泄漏
- 使用对象池
2. StackOverflowError(栈溢出错误)
产生原因:当方法调用栈深度过大时抛出
常见场景:
- 无限递归
- 深层方法调用链
示例:
java
public class StackOverflowErrorExample {
public static void main(String[] args) {
try {
recursiveMethod();
} catch (StackOverflowError e) {
System.out.println("栈溢出错误: " + e.getMessage());
}
}
public static void recursiveMethod() {
recursiveMethod(); // 无限递归
}
}解决方案:
- 避免无限递归
- 优化递归算法
- 增加栈大小
示例:异常处理的综合应用
示例 1:用户输入验证
java
import java.util.Scanner;
public class InputValidationExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.print("请输入一个整数: ");
String input = scanner.nextLine();
int number = Integer.parseInt(input);
System.out.println("输入的整数是: " + number);
} catch (NumberFormatException e) {
System.out.println("输入错误: 请输入有效的整数");
} finally {
scanner.close();
}
}
}示例 2:文件操作
java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FileOperationExample {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("文件操作错误: " + e.getMessage());
}
}
}示例 3:数组操作
java
public class ArrayOperationExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
try {
System.out.print("请输入要访问的数组索引: ");
Scanner scanner = new Scanner(System.in);
int index = scanner.nextInt();
if (index < 0 || index >= numbers.length) {
throw new ArrayIndexOutOfBoundsException("索引超出范围: " + index);
}
System.out.println("索引 " + index + " 处的元素是: " + numbers[index]);
scanner.close();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组索引越界: " + e.getMessage());
} catch (java.util.InputMismatchException e) {
System.out.println("输入错误: 请输入有效的整数");
}
}
}最佳实践
了解常见异常:熟悉常见的异常类型及其产生原因
捕获具体异常:尽量捕获具体的异常类型,而不是捕获所有异常
处理异常:捕获异常后应该进行适当的处理,而不是简单地打印错误信息
预防异常:在可能发生异常的地方进行预防,如 null 检查、范围检查等
使用 try-with-resources:对于需要释放的资源,使用 try-with-resources 自动关闭
记录异常:对于重要的异常,应该记录到日志中
避免过度使用异常:不要使用异常来控制正常的程序流程
总结
Java 中的异常分为两大类:
运行时异常(非检查型异常):继承自
RuntimeException,编译器不要求必须处理,如NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException等检查型异常:直接继承自
Exception,编译器要求必须处理,如IOException、FileNotFoundException、SQLException等
此外,还有 错误(Error),如 OutOfMemoryError、StackOverflowError,这些通常是严重的问题,程序无法恢复。
通过了解常见的异常类型及其产生原因,我们可以更好地预防和处理异常,提高程序的健壮性和可维护性。
