为什么在这种情况下允许引发已检查的异常类型?

我偶然注意到这个语句(从一些更复杂的代码中提取)编译:throw

void foo() {
    try {

    } catch (Throwable t) {
        throw t;
    }
}

在一个短暂但快乐的时刻,我以为检查过的异常终于决定已经死了,但它仍然对此感到遗憾:

void foo() {
    try {

    } catch (Throwable t) {
        Throwable t1 = t;
        throw t1;
    }
}

该块不必为空;它似乎可以有代码,只要该代码不引发已检查的异常。这似乎是合理的,但我的问题是,语言规范中的哪个规则描述了这种行为?据我所知,§14.18 throw 语句明确禁止它,因为表达式的类型是已检查的异常,并且它不会被捕获或声明为被抛出。(?)tryt


答案 1

这是因为在 Java 7 中引入的 Project Coin 中包含的更改允许通过重新抛出原始异常进行常规异常处理。下面是一个在 Java 7 中工作但不适用于 Java 6 的示例:

public static demoRethrow() throws IOException {
    try {
        throw new IOException("Error");
    }
    catch(Exception exception) {
        /*
         * Do some handling and then rethrow.
         */
        throw exception;
    }
}

您可以在此处阅读解释更改的整篇文章。


答案 2

我认为您提到的 §14.18 The throw Statement 中的措辞是 JLS 中的一个错误 — 该文本本应使用 Java SE 7 进行更新,但事实并非如此。

描述预期行为的 JLS 文本位位于 §11.2.2 语句的异常分析中:

如果语句的抛出表达式是子句 C 的最终或实际上是最终的异常参数,则该语句可以引发异常类 E iff:throwcatch

  • E 是声明 C 的语句块可以引发的异常类;和trytry
  • E 是与 C 的任何可捕获异常类兼容的赋值;和
  • E 与同一语句中 C 左侧声明的子句的任何可捕获异常类的赋值不兼容。catchtry

第一个项目符号是相关的;因为 -子句参数实际上是 final(意味着它永远不会被赋值、递增或递减;参见 §4.12.4 final Variables),因此只能抛出块可以抛出的东西。catchtthrow ttry

但正如你所说,§14.18中的编译时检查没有考虑到这一点。§11.2.2 不决定允许什么,不允许什么;相反,它应该是对可以抛出的各种限制的后果的分析。(此分析确实反馈到规范中更具规范性的部分 - §14.18本身在其第二个项目符号点中使用它 - 但§14.18不能只是说“如果它抛出一个异常,它就不能根据§11.2.2抛出”,因为这是一个编译时错误“,因为那将是循环的。

因此,我认为§14.18需要调整以适应§11.2.2的意图。

很好找!