18

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

void foo() {
    try {

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

有一个短暂而快乐的时刻,我认为受检异常最终决定已经死了,但它仍然对此感到高兴:

void foo() {
    try {

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

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

4

3 回答 3

13

这是因为在 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;
    }
}

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

于 2014-07-27T14:10:52.590 回答
8

我认为您提到的§14.18 The throwStatement中的措辞是 JLS 中的一个错误——本应使用 Java SE 7 更新的文本,但没有。

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

抛出的throw表达式是catch子句 C 的最终或有效最终异常参数的语句可以抛出异常类 E 当且仅当:

  • E是声明Ctry的语句块可以抛出的异常类;try
  • E 是与 C 的任何可捕获异常类兼容的赋值;和
  • catchE 与在同一try语句中声明在 C 左侧的子句的任何可捕获异常类的赋值都不兼容。

第一个要点是相关的;因为catch-clause 参数t实际上是最终的(意味着它永远不会分配或递增或递减;请参阅§4.12.4final变量),throw t只能抛出try块可以抛出的东西。

但正如您所说,第 14.18 节中的编译时检查并没有对此做出任何考虑。§11.2.2 不决定什么是允许的,什么是不允许的;相反,它应该是对可以抛出的各种限制的后果的分析。(这种分析确实反馈到规范的更规范的部分——第 14.18 节本身在其第二个要点中使用它——但第 14.18 节不能只说“如果它抛出一个它不能抛出的异常,这是一个编译时错误按照第 11.2.2 节“抛出,因为那将是循环的。)

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

好发现!

于 2014-07-27T19:49:45.073 回答
3

此行为在11.2 中的 JLS 中有详细描述。异常的编译时检查

抛出的throw表达式是catch子句 C 的最终或有效最终异常参数的语句可以抛出异常类 E 当且仅当:

  • E 是声明 Ctry的语句块可以抛出的异常类try;和

  • E 是与 C 的任何可捕获异常类兼容的赋值;和

  • catchE 与在同一try 语句中声明在 C 左侧的子句的任何可捕获异常类的赋值都不兼容。

(强调我的。)

您的第二个示例失败,因为t1不是“catch子句的异常参数”。

于 2014-07-27T19:49:09.907 回答