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

Posted

技术标签:

【中文标题】捕获异常并重新抛出它,但这不是异常【英文标题】:Catching an Exception and rethrowing it, but it's not an Exception 【发布时间】:2020-02-13 10:00:58 【问题描述】:

我偶然发现了如下代码:

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


void doSomething() 
    throw new RuntimeException();

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

我的期望是我必须在run()-方法中声明throws Exception

额外信息

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

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语言规范中的什么允许run()方法不需要在上面的代码sn-ps中声明throws Exception? (如果我要添加它,IntelliJ 会警告我 Exception 永远不会被抛出)。

【问题讨论】:

有趣。你用的是什么编译器?如果它是 IDE 编译器,请检查 javac - 我遇到过 Eclipse 编译器更宽松的情况。 我可以在 openjdk-8 上重现此行为。值得注意的是,使用-source 1.6 标志进行编译会引发预期的编译错误。使用源代码兼容性 7 进行编译不会引发编译错误 自 Java 7 以来,编译器似乎是 smarter,并且对可能引发的实际异常进行了更多检查。 这个问题不是重复的,答案可以在我提供的链接中找到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. currently marked duplicate 肯定是相关的,但没有提供足够详细的答案 IMO。 一个链接到 JLS 在 cmets 到答案那里,除了没有信息。 【参考方案1】:

我没有像您在问题中提出的那样扫描JLS,所以请对这个答案持保留态度。我想发表评论,但它太大了。


我有时觉得很有趣,javac 在某些情况下是多么“聪明”(比如在你的情况下),但还有很多其他事情需要稍后由JIT 处理。在这种情况下,只是编译器“可以告诉”只有RuntimeException 会被捕获。这很明显,这是您在doSomething 中唯一输入的内容。如果您将代码稍微更改为:

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

你会看到不同的行为,因为现在javac 可以告诉你有一个新的Exception 你正在扔,与你抓到的那个无关。

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

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编译的

【讨论】:

我在某个链接中读到有人提供,如果您在 catch 块中重新分配捕获的异常,那么编译器将无法智能。我认为在这种情况下也适用类似的情况。编译器知道会抛出ex2异常,它最初是作为Exception创建的,但后来被重新分配给ex,因此编译器无法智能。 @SimonForsberg 对JLS 充满热情的人可能会来提供所需的报价来证明这一点;不幸的是,我没有它们。 作为记录,当我更改 catch 块以包含将捕获的异常重新分配给自身 (ex = ex;) 时,不再应用启发式。此行为似乎适用于从 7 到 11 甚至可能是 13 的所有源级别 看看this问题,这也是重复的。这个和可能的 dup 的 dup 解释了它并链接到 JLS。

以上是关于捕获异常并重新抛出它,但这不是异常的主要内容,如果未能解决你的问题,请参考以下文章

Azure 函数应该记录错误还是抛出异常?

Python Cookbook(第3版)中文版:14.10 重新抛出被捕获的异常

在iOS中捕获崩溃和异常[重复]

捕获和重新抛出 .NET 异常的最佳实践

捕获和重新抛出异常的最佳实践是啥?

第十二章 重新抛出异常与异常链