为什么不抛出异常的代码允许捕获已检查的异常?

Why is catching checked exceptions allowed for code that does not throw exceptions?

在 Java 中,抛出 checked 异常(Exception 或其子类型 - IOException、InterruptedException 等)的方法必须声明 抛出 语句:

public abstract int read() throws IOException;

未声明 [​​=14=] 语句的方法 不能 抛出已检查的异常。

public int read() { // does not compile
    throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown

但是在安全方法中捕获检查异常在java中仍然是合法的:

public void safeMethod() { System.out.println("I'm safe"); }

public void test() { // method guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) { // catching checked exception java.lang.Exception
        throw e; // so I can throw... a checked Exception?
    }
}

实际上,没有。有点好笑:编译器知道 e 不是已检查的异常并允许重新抛出它。事情甚至有点荒谬,这段代码无法编译:

public void test() { // guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) {        
        throw (Exception) e; // seriously?
    }
}
// Error: unreported exception java.lang.Exception; must be caught or declared to be thrown

第一个片段是提问的动机。

编译器知道不能在安全方法中抛出已检查的异常 - 所以也许它应该只允许捕获未检查的异常?


回到主要问题 - 是否有任何理由以这种方式实现捕获已检查的异常?这只是设计中的缺陷还是我遗漏了一些重要因素——可能是向后不兼容?如果在这种情况下只允许捕获 RuntimeException,可能会出现什么问题?非常感谢示例。

Java 7介绍more inclusive exception type checking.

However, in Java SE 7, you can specify the exception types FirstException and SecondException in the throws clause in the rethrowException method declaration. The Java SE 7 compiler can determine that the exception thrown by the statement throw e must have come from the try block, and the only exceptions thrown by the try block can be FirstException and SecondException.

这段话讲的是一个try块,专门抛出FirstExceptionSecondException;即使 catch 块抛出 Exception,该方法只需要声明它抛出 FirstExceptionSecondException,而不是 Exception:

public void rethrowException(String exceptionName)
 throws FirstException, SecondException {
   try {
     // ...
   }
   catch (Exception e) {
     throw e;
   }
 }

这意味着编译器可以检测到 test 中唯一可能抛出的异常类型是 ErrorRuntimeException,这两种都不需要被捕获。当你 throw e; 时,它可以告诉你,即使静态类型是 Exception,它也不需要声明或重新捕获。

但是当你 castException 时,这绕过了那个逻辑。现在编译器将其视为需要捕获或声明的普通 Exception

将此逻辑添加到编译器的主要原因是允许程序员在重新抛出捕获那些特定子类型的通用 Exception 时仅在 throws 子句中指定特定子类型。但是,在这种情况下,它允许您捕获一般 Exception 而不必在 throws 子句中声明 any 异常,因为没有特定类型可以被抛出的是已检查的异常。

引用 Java Language Specification, §11.2.3:

It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case that the try block corresponding to the catch clause can throw a checked exception class that is a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception.

我猜这条规则起源于 Java 7 之前很久,当时 multi-catches 并不存在。因此,如果你有一个可以抛出大量异常的 try 块,捕获所有异常的最简单方法是捕获一个普通的 superclass(在最坏的情况下,Exception,或者 Throwable 如果你也想捕捉 Errors)。

请注意,您可能 not 捕获与实际抛出的内容完全无关的异常类型 - 在您的示例中,捕获 [=13 的任何子 class =] 不是 RuntimeException 将是一个错误:

try {
    System.out.println("hello");
} catch (IOException e) {  // compilation error
    e.printStackTrace();
}


OP 编辑​​: 答案的主要部分是问题示例仅适用于异常 class。通常不允许在代码的随机位置捕获已检查的异常。抱歉,如果我使用这些示例混淆了某些人。

这里的问题是 checked/unchecked 异常限制会影响您的代码允许 throw 的内容,而不是 catch[=55] 允许的内容=].虽然您仍然可以 捕获 任何类型的 Exception,但您唯一可以实际再次抛出的是未经检查的。 (这就是为什么将未经检查的异常转换为已检查的异常会破坏您的代码的原因。)

Exception捕获未检查的异常是有效的,因为未检查的异常(a.k.a.RuntimeExceptions)是Exception的子class,它遵循标准多态规则;它不会将捕获的异常转换为 Exception,就像将 String 存储在 Object 中不会将 String 转换为 一个 Object。多态性意味着可以包含 Object 的变量可以包含从 Object 派生的任何内容(例如 String)。同样,由于 Exception 是所有异常类型的超 class,类型 Exception 的变量可以保存从 Exception 派生的任何 class,而无需转动对象变成一个Exception。考虑一下:

import java.lang.*;
// ...
public String iReturnAString() { return "Consider this!"; }
// ...
Object o = iReturnAString();

尽管变量的类型是 Objecto 仍然存储 String,不是吗?同样,在您的代码中:

try {
    safeMethod();
} catch (Exception e) { // catching checked exception
    throw e; // so I can throw... a checked Exception?
}

这意味着实际上 "catch anything compatible with class Exception (i.e. Exception and anything derived from it)."其他语言也使用类似的逻辑;例如,在 C++ 中,捕获 std::exception 也会捕获 std::runtime_errorstd::logic_errorstd::bad_alloc、任何正确定义的用户创建的异常,等等,因为它们都是派生自 std::exception.

tl;dr:您没有捕获 checked 异常,您捕获的是 any 异常。如果将异常转换为已检查的异常类型,则该异常只会成为已检查的异常。