Java 异常层次结构 - 未选中和已检查的异常

所有 Java 异常都是 Exception 类层次结构中的类的实例。这可以表示如下:

  • java.lang.Throwable - 这是所有异常类的基类。它的方法和构造函数实现了所有异常共有的一系列功能。

笔记:

  1. 检查未检查的异常之间的区别如下所述。
  2. ThrowableExceptionRuntimeException 类应该被视为 abstract; 看到陷阱 - 投掷 Throwable,Exception,Error 或 RuntimeException
  3. 在应用程序尝试恢复不安全或不明智的情况下,JVM 会抛出 Error 异常。
  4. 声明 Throwable 的自定义子类型是不明智的。Java 工具和库可以假设 ErrorExceptionThrowable 的唯一直接子类型,如果该假设不正确则行为不端。

已检查与未经检查的异常情况

某些编程语言中对异常支持的批评之一是很难知道给定方法或过程可能抛出哪些异常。鉴于未处理的异常可能导致程序崩溃,这可能会使异常成为脆弱性的来源。

Java 语言通过已检查的异常机制解决了这个问题。首先,Java 将异常分为两类:

  • 检查的异常通常表示应用程序应该能够处理的预期事件。例如,IOException 及其子类型表示 I / O 操作中可能发生的错误情况。示例包括,文件打开失败,因为文件或目录不存在,网络读取和写入失败,因为网络连接已断开等等。

  • 未经检查的异常通常表示应用程序无法处理的意外事件。这些通常是应用程序中的错误的结果。

(在下文中,抛出是指显式抛出的任何异常(通过 throw 语句),或隐式抛出(在失败的解除引用中,类型转换等)。类似地,传播是指抛出的异常嵌套调用,并没有在该调用中捕获。下面的示例代码将说明这一点。)

检查异常机制的第二部分是对可能发生检查异常的方法有限制:

  • 当在方法中抛出或传播检查的异常时,它必须被方法捕获,或者在方法的 throws 子句中列出。 ( 本例中描述了 throws 子句的重要性。)
  • 当在初始化程序块中抛出或传播已检查的异常时,必须将其捕获到该块。
  • 在字段初始化表达式中,方法调用无法传播已检查的异常。 (没有办法捕获这样的异常。)

简而言之,必须处理或声明已检查的异常。

这些限制不适用于未经检查的异常。这包括隐式抛出异常的所有情况,因为所有这些情况都会抛出未经检查的异常。

检查异常示例

这些代码段旨在说明已检查的异常限制。在每种情况下,我们都会显示带有编译错误的代码版本,以及更正错误的第二个版本。

// This declares a custom checked exception.
public class MyException extends Exception {
    // constructors omitted.
}

// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
    // constructors omitted.
}

第一个示例显示如果不应在方法中处理它们,如何将显式抛出的已检查异常声明为抛出

// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
    int i = 1 / 0;                // Compiles OK, throws ArithmeticException
    if (flag) {
        throw new MyException();  // Compilation error
    } else {
        throw new MyException2(); // Compiles OK
    }
}

// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
    int i = 1 / 0;                // Compiles OK, throws ArithmeticException
    if (flag) {
        throw new MyException();  // Compilation error
    } else {
        throw new MyException2(); // Compiles OK
    }
}

第二个示例显示了如何处理传播的已检查异常。

// INCORRECT 
public void methodWithPropagatedCheckedException() {
    InputStream is = new FileInputStream("someFile.txt");  // Compilation error
    // FileInputStream throws IOException or a subclass if the file cannot 
    // be opened.  IOException is a checked exception.
    ...
}

// CORRECTED (Version A) 
public void methodWithPropagatedCheckedException() throws IOException {
    InputStream is = new FileInputStream("someFile.txt"); 
    ...
}

// CORRECTED (Version B) 
public void methodWithPropagatedCheckedException() {
    try {
        InputStream is = new FileInputStream("someFile.txt"); 
        ...
    } catch (IOException ex) {
        System.out.println("Cannot open file: " + ex.getMessage());
    }
}

最后一个示例显示了如何在静态字段初始值设定项中处理已检查的异常。

// INCORRECT
public class Test {
    private static final InputStream is = 
            new FileInputStream("someFile.txt");  // Compilation error
}

// CORRECTED
public class Test {
    private static final InputStream is;
    static {
        InputStream tmp = null;
        try {
            tmp = new FileInputStream("someFile.txt");
        } catch (IOException ex) {
            System.out.println("Cannot open file: " + ex.getMessage());
        }
        is = tmp;
    }
}

请注意,在最后一种情况下,我们还必须处理 is 不能多次分配的问题,而且即使在异常的情况下也必须分配。