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