捕获异常并重新抛出它,但它不是异常

Catching an Exception and rethrowing it, but it's not an Exception

提问人:Simon Forsberg 提问时间:10/16/2019 最后编辑:Simon Forsberg 更新时间:10/4/2022 访问量:1780

问:

我偶然发现了如下所示的代码:

void run() {
    try {
        doSomething();
    } catch (Exception ex) {
        System.out.println("Error: " + ex);
        throw ex;
    }
}

void doSomething() {
    throw new RuntimeException();
}

这段代码让我感到惊讶,因为看起来 -method 能够抛出一个 ,因为它捕获然后重新抛出它,但该方法没有被声明为抛出,显然不需要。这段代码编译得很好(至少在 Java 11 中是这样)。run()ExceptionExceptionException

我的期望是我必须在 -method 中声明。throws Exceptionrun()

额外信息

以类似的方式,如果被声明为抛出,那么只需要在 -方法中声明,即使被捕获并重新抛出。doSomethingIOExceptionIOExceptionrun()Exception

void run() throws IOException {
    try {
        doSomething();
    } catch (Exception ex) {
        System.out.println("Error: " + ex);
        throw ex;
    }
}

void doSomething() throws IOException {
    // ... whatever code you may want ...
}

问题

Java 通常喜欢清晰,这种行为背后的原因是什么?一直都是这样吗?Java 语言规范中哪些内容允许该方法不需要在上面的代码片段中声明?(如果我添加它,IntelliJ 会警告我永远不会抛出它)。run()throws ExceptionException

Java 异常 抛出

评论

3赞 M. Prokhorov 10/16/2019
有趣。您使用的是什么编译器?如果是IDE编译器,请检查-我一直遇到Eclipse编译器更宽松的情况。javac
2赞 Vogel612 10/16/2019
我可以在 openjdk-8 上重现这种行为。值得注意的是,使用该标志进行编译会引发预期的编译错误。使用源代码兼容性 7 进行编译不会引发编译错误-source 1.6
1赞 Michał Krzywański 10/16/2019
自 Java 7 以来,编译器似乎更聪明,并且对可能抛出的实际异常进行了更多检查。
2赞 Michał Krzywański 10/16/2019
这个问题不是重复的,答案可以在我提供的链接中找到In detail, in Java SE 7 and later, when you declare one or more exception types in a catch clause, and rethrow the exception handled by this catch block, the compiler verifies that the type of the rethrown exception meets the following conditions : 1. 1. The try block is able to throw it. 2. There are no other preceding catch blocks that can handle it. 3. It is a subtype or supertype of one of the catch clause's exception parameters.
2赞 Simon Forsberg 10/16/2019
当前标记的重复项肯定是相关的,但没有提供足够详细的答案 IMO。在答案的评论中有一个指向 JLS 的链接,除此之外没有任何信息。

答:

0赞 Eugene 10/18/2019 #1

我没有像你在问题中问的那样扫描过,所以请对这个答案持保留态度。我想把它作为一个评论,但它太大了。JLS


我有时觉得很有趣,在某些情况下(就像你的情况一样)非常“聪明”,但还有很多其他事情需要稍后处理。在这种情况下,只是编译器“可以告诉”只有 a 会被捕获。这很明显,这是你唯一投入的东西.如果您稍微将代码更改为:javacJITRuntimeExceptiondoSomething

void run() {
    try {
        doSomething();
    } catch (Exception ex) {
        Exception ex2 = new Exception();
        System.out.println("Error: " + ex);
        throw ex2;
    }
}

你会看到一个不同的行为,因为现在可以看出你正在抛出一个新的,与你抓住的那个无关。javacException

但事情远非理想,您可以通过以下方式再次“欺骗”编译器:

void run() {
    try {
        doSomething();
    } catch (Exception ex) {
        Exception ex2 = new Exception();
        ex2 = ex;
        System.out.println("Error: " + ex);
        throw ex2;
    }
}

IMO,因为它不应该再次失败,但它确实如此。ex2 = ex;

以防万一这是用javac 13+33

评论

0赞 Simon Forsberg 10/18/2019
我在一些链接中读到有人提供,如果您在 catch-block 中重新分配捕获的异常,那么编译器就无法智能。我认为类似的东西适用于这种情况。编译器知道会抛出异常,它最初是作为 an 创建的,但后来被重新分配给 ,因此编译器无法智能。ex2Exceptionex
0赞 Eugene 10/18/2019
@SimonForsberg对 MAY 充满热情的人可能会来提供所需的报价来证明这一点;不幸的是,我没有它们。JLS
0赞 Vogel612 10/18/2019
作为记录,当我更改 catch 块以包含捕获异常对自身的重新分配 () 时,不再应用启发式方法。这种行为似乎适用于从 7 到 11 甚至可能 13 的所有源级别ex = ex;
0赞 Michał Krzywański 10/18/2019
看看这个问题,这也是一个重复的问题。这个和可能的 dup 的 dup 解释了它,并链接到 JLS。
-1赞 DRodriguez 10/4/2022 #2

如果未经检查的异常可以通过方法或构造函数的执行引发并在方法或构造函数边界之外传播,则不需要在方法或构造函数的 throws 子句中声明这些异常。RuntimeException 未选中。

评论

0赞 Simon Forsberg 10/4/2022
这个答案没有提供我的问题中尚未显示的其他信息。我已经知道选中的未选中异常之间的区别。