异常

1. 异常概述 #

异常是程序执行过程中出现的意外事件,会中断程序的正常流程。Java使用异常处理机制来处理运行时错误,使得程序更加健壮。

2. 异常层次结构 #

Java的所有异常都是java.lang.Throwable类的子类,主要分为两大类:

  1. Error:表示严重的问题,通常是不可恢复的系统级错误。
  2. Exception:表示可以被程序处理的异常情况。
        Throwable
         /     \
      Error   Exception
                 \
            RuntimeException

3. 检查型异常vs非检查型异常 #

3.1 检查型异常(Checked Exceptions) #

  • 除了RuntimeException及其子类之外的所有异常。
  • 必须在代码中显式处理或声明抛出。
  • 例如:IOException, SQLException

3.2 非检查型异常(Unchecked Exceptions) #

  • RuntimeException及其子类。
  • 不需要在代码中显式处理。
  • 例如:NullPointerException, ArrayIndexOutOfBoundsException

4. 异常处理 #

4.1 try-catch 块 #

try {
    // 可能抛出异常的代码
} catch (ExceptionType1 e1) {
    // 处理 ExceptionType1
} catch (ExceptionType2 e2) {
    // 处理 ExceptionType2
} finally {
    // 无论是否发生异常都会执行的代码
}

4.2 多重捕获 #

Java 7引入的特性,允许在一个catch块中捕获多个异常:

try {
    // 可能抛出异常的代码
} catch (IOException | SQLException e) {
    // 处理 IOException 或 SQLException
}

4.3 try-with-resources #

Java 7引入的特性,用于自动关闭实现了AutoCloseable接口的资源:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    // 使用 br
} catch (IOException e) {
    // 处理异常
}

5. 抛出异常 #

使用throw关键字抛出异常:

if (condition) {
    throw new IllegalArgumentException("Invalid argument");
}

6. 声明异常 #

使用throws关键字在方法签名中声明可能抛出的检查型异常:

public void readFile(String path) throws IOException {
    // 方法实现
}

7. 自定义异常 #

创建自定义异常类:

public class CustomException extends Exception {
    public CustomException(String message) {
        super(message);
    }
}

8. 异常链 #

在捕获一个异常后抛出另一个异常,同时保留原始异常信息:

try {
    // 可能抛出异常的代码
} catch (OriginalException e) {
    throw new WrapperException("A problem occurred", e);
}

9. 最佳实践 #

  1. 只在异常情况下使用异常。
  2. 捕获具体的异常,而不是笼统地捕获Exception
  3. 不要捕获ThrowableError
  4. finally块中清理资源,或使用try-with-resources。
  5. 记录异常信息,有助于调试。
  6. 适当地封装异常,不要暴露敏感信息。

10. 常见异常类型 #

  • NullPointerException: 尝试使用null对象
  • ArrayIndexOutOfBoundsException: 数组索引越界
  • IllegalArgumentException: 方法接收到不合法的参数
  • IOException: 输入/输出操作失败
  • SQLException: 数据库访问错误

理解和正确使用Java异常处理机制可以显著提高程序的健壮性和可维护性。通过合理的异常处理,我们可以优雅地处理错误情况,提供有意义的错误消息,并确保程序能够从错误中恢复或优雅地退出。

11. 自定义异常的类型 #

11.1 检查型异常 vs 非检查型异常 #

在之前的例子中:

public class CustomException extends Exception {
    public CustomException(String message) {
        super(message);
    }
}

这个CustomException是一个检查型异常。这是因为它直接继承自Exception类,而不是RuntimeException类。

11.2 自定义检查型异常 #

  • 继承自Exception类或其任何子类(除了RuntimeException及其子类)
  • 必须被显式捕获或在方法签名中声明抛出
  • 用于表示程序可以合理地预期和恢复的异常情况

例如:

public class CustomCheckedException extends Exception {
    public CustomCheckedException(String message) {
        super(message);
    }
}

11.3 自定义非检查型异常 #

  • 继承自RuntimeException类或其子类
  • 不需要显式捕获或声明
  • 通常用于表示编程错误或不可恢复的状态

例如:

public class CustomUncheckedException extends RuntimeException {
    public CustomUncheckedException(String message) {
        super(message);
    }
}

11.4 选择建议 #

  1. 使用检查型异常:

    • 当你希望强制调用者处理异常情况
    • 当异常是可以预期和恢复的
    • 例如:文件操作、网络连接问题
  2. 使用非检查型异常:

    • 当异常表示编程错误(如空指针、非法参数)
    • 当异常是不可恢复的
    • 当你不希望强制每个调用者都处理这个异常

11.5 示例 #

检查型异常示例:

public void readImportantFile() throws CustomCheckedException {
    if (!fileExists()) {
        throw new CustomCheckedException("Important file does not exist");
    }
    // 读取文件的代码
}

// 使用时必须处理或声明抛出
try {
    readImportantFile();
} catch (CustomCheckedException e) {
    // 处理异常
}

非检查型异常示例:

public void processData(String data) {
    if (data == null) {
        throw new CustomUncheckedException("Data cannot be null");
    }
    // 处理数据的代码
}

// 使用时不强制处理
processData(someData);