Skip to content

15.1 什么是异常?

异常的概念

异常(Exception)是指程序运行过程中出现的非正常情况,这些情况会中断程序的正常执行流程。在 Java 中,异常是一种对象,它表示程序运行时发生的错误或异常情况。

异常的分类

Java 中的异常分为两大类:

  1. Checked Exception(检查型异常):编译器要求必须处理的异常,必须通过 try-catch 捕获或通过 throws 声明
  2. Unchecked Exception(非检查型异常):编译器不要求必须处理的异常,包括 RuntimeException 及其子类

异常的层次结构

Java 中的异常层次结构如下:

  • Throwable:所有异常和错误的父类
    • Error:表示严重的错误,通常程序无法恢复,如 OutOfMemoryErrorStackOverflowError
    • Exception:表示可以被捕获和处理的异常
      • RuntimeException:运行时异常,属于非检查型异常
      • 其他异常:检查型异常,如 IOExceptionSQLException

异常的产生

异常的产生有两种方式:

  1. 自动产生:当程序运行过程中遇到错误时,JVM 会自动创建并抛出异常
  2. 手动抛出:使用 throw 关键字手动抛出异常

异常的处理

异常的处理方式有两种:

  1. 捕获异常:使用 try-catch 语句捕获并处理异常
  2. 声明异常:使用 throws 关键字声明方法可能抛出的异常,由调用者处理

异常的特点

  1. 异常是对象:异常是 Throwable 类或其子类的实例
  2. 异常会中断程序执行:当异常发生时,程序会跳转到异常处理代码
  3. 异常可以传递:异常可以从方法传递到调用者
  4. 异常可以被捕获和处理:使用 try-catch 语句捕获和处理异常

异常与错误的区别

特性异常(Exception)错误(Error)
类型可恢复的问题严重的问题
处理方式可以捕获和处理通常无法处理
示例IOExceptionNullPointerExceptionOutOfMemoryErrorStackOverflowError
继承关系继承自 Exception继承自 Error

异常的作用

  1. 分离错误处理代码:将正常业务逻辑与错误处理逻辑分离
  2. 提高程序的健壮性:即使出现异常,程序也能继续执行
  3. 提供错误信息:异常对象包含错误信息,有助于调试
  4. 简化错误处理:使用统一的异常处理机制,减少代码重复

示例:异常的产生和处理

示例 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 中抛出异常");
    }
}

常见的异常类型

运行时异常(非检查型异常)

  1. NullPointerException:空指针异常,当尝试访问 null 对象的成员时抛出
  2. ArrayIndexOutOfBoundsException:数组索引越界异常,当访问数组的无效索引时抛出
  3. ArithmeticException:算术异常,如除以零
  4. IllegalArgumentException:非法参数异常,当方法接收到无效参数时抛出
  5. ClassCastException:类型转换异常,当尝试将对象转换为不兼容的类型时抛出
  6. NumberFormatException:数字格式异常,当尝试将字符串转换为数字失败时抛出

检查型异常

  1. IOException:输入/输出异常,如文件读写错误
  2. SQLException:数据库操作异常
  3. ClassNotFoundException:类未找到异常
  4. InterruptedException:线程中断异常
  5. FileNotFoundException:文件未找到异常

异常处理的最佳实践

  1. 捕获具体的异常:尽量捕获具体的异常类型,而不是捕获所有异常

  2. 不要捕获所有异常:避免使用 catch (Exception e) 捕获所有异常

  3. 处理异常:捕获异常后应该进行适当的处理,而不是简单地打印错误信息

  4. 使用 finally 块:对于需要释放的资源,使用 finally 块确保资源被释放

  5. 抛出有意义的异常:手动抛出异常时,提供有意义的错误信息

  6. 不要忽略异常:不要捕获异常后不做任何处理

  7. 使用 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 中处理运行时错误的机制,它可以帮助我们:

  1. 分离错误处理代码:将正常业务逻辑与错误处理逻辑分离
  2. 提高程序的健壮性:即使出现异常,程序也能继续执行
  3. 提供错误信息:异常对象包含错误信息,有助于调试
  4. 简化错误处理:使用统一的异常处理机制,减少代码重复

Java 中的异常分为两大类:

  • Checked Exception:编译器要求必须处理的异常
  • Unchecked Exception:编译器不要求必须处理的异常

异常的处理方式有两种:

  • 捕获异常:使用 try-catch 语句捕获并处理异常
  • 声明异常:使用 throws 关键字声明方法可能抛出的异常

通过合理使用异常处理机制,可以提高代码的可读性、可维护性和健壮性。

© 2026 编程马·菜鸟教程 版权所有