Java 为什么try..finally块没有将原始异常注册为已抑制?

Java 为什么try..finally块没有将原始异常注册为已抑制?,java,exception-handling,try-finally,Java,Exception Handling,Try Finally,使用以下代码: try { throw new RuntimeException ("main"); } finally { throw new RuntimeException ("finally"); } try(FileInputStream fstream = new FileInputStream("test")) { fstream.read(); } 我得到这个结果: Exception in thread "main" java.lang.Runtim

使用以下代码:

try {
    throw new RuntimeException ("main");
}
finally {
    throw new RuntimeException ("finally");
}
try(FileInputStream fstream = new FileInputStream("test")) {
    fstream.read();
}
我得到这个结果:

Exception in thread "main" java.lang.RuntimeException: finally
        at test.main(test.java:12)
然而,在Java7中添加了抑制异常,当
最终
块本身因异常而失败时,该语言将原始“main”异常注册为抑制异常,这难道不是合乎逻辑的吗?目前,我必须手动模拟:

try {
    throw new RuntimeException ("main");
}
catch (RuntimeException exception) {
    try {
        throw new RuntimeException ("finally");
    }
    catch (RuntimeException exception2) {
        exception2.addSuppressed (exception);
        throw exception2;
    }
}
要获得更有用的(了解正在发生的事情)结果:


编辑:以澄清我的疑惑。当前的Java版本是8,抑制异常不是一个全新的特性。但是,
try..finally
仍然没有包含它们。有什么东西可以防止这种情况发生吗?

因为try with resources是语法糖,Java编译器不会以同样的方式扩展常规try finally块

请看下面的代码:

try {
    throw new RuntimeException ("main");
}
finally {
    throw new RuntimeException ("finally");
}
try(FileInputStream fstream = new FileInputStream("test")) {
    fstream.read();
}
编译然后反编译(使用IntelliJ IDEA)时,它看起来如下所示:

FileInputStream fstream = new FileInputStream("test");
Throwable var2 = null;

try {
    fstream.read();
} catch (Throwable var19) {
    var2 = var19;
    throw var19;
} finally {
    if(fstream != null) {
        if(var2 != null) {
            try {
                fstream.close();
            } catch (Throwable var17) {
                var2.addSuppressed(var17);
            }
        } else {
            fstream.close();
        }
    }
}
鉴于本守则:

FileInputStream fstream = new FileInputStream("test");
try {
    fstream.read();
} finally {
    fstream.close();
}
编译和反编译时看起来完全相同

现在,可以肯定的是,所有的
最终
块都应该以与上面相同的方式进行扩展,但由于某种原因,要么被忽略,要么被决定不进行扩展


我建议您这样做,因为我认为这是一个合理的功能。

这不是一个权威的答案,但看起来这样的更改可能会破坏兼容性,或者
尝试使用资源
最终尝试
会相互不一致

try with resources
中的语义是传播从
try
块抛出的异常,调用注册为抑制的
close()
方法时抛出的异常。从实用的角度来看,这是有意义的,您希望捕获“真正的”异常,然后如果愿意,还可以处理未关闭的资源

但是在
try finally
中,传播的是
finally
中抛出的异常(而
try
中的异常被“吞没”),因此我们可以选择三种糟糕的解决方案:

  • 颠倒
    try finally
    的逻辑,使其与
    try with resources
    保持一致,并破坏代码(或者更确切地说,bug)与以前所有代码的兼容性
  • 继续传播“
    close()
    ”异常,其中注册为抑制的
    try
    异常使这两个构造不一致
  • 一切都保持原样

  • 主观上,我认为3不如1或2糟糕,尽管很容易提出相反的观点。然而,我怀疑这是语言开发人员面临的一个困境,他们碰巧选择了选项3。

    被抑制的异常不应该是最后在
    中抛出的异常吗?@StenSoft:即使可能更符合逻辑,这也会完全破坏向后兼容性。老实说,我不理解投票结果。这是一个完全合理的问题。可能与有关,因为您可以使用嵌套的try-catch块、一个包含异常的局部变量和一个rethrow来相当容易地模拟这一点,所以我认为VM或JLS中没有任何固有的东西可以阻止这一点。我将这个问题理解为“为什么Java编译器不以同样的方式扩展常规try finally块”,尽管我认为这个答案仍然添加了一些有用的东西?“它之所以没有发生,是因为它没有得到实施。至于为什么没有实现,我们只能猜测或询问Oracle(可能是通过打开一个功能请求)。我认为在现实生活中,finally中的异常通常会因为关闭资源异常而发生。这就是为什么我认为必须使用自动关闭接口的原因例如:类清理实现自动关闭{@Override public void close()抛出异常{Cleanup();}}。。。尝试(Cleanup Cleanup=new Cleanup()){//some exception should here}在字节码级别,没有
    最终
    。第二个变体“在编译和反编译时看起来完全相同”的原因是,反编译器识别字节码模式并重建语言结构。因此,您基本上演示的是关于第一个代码示例的反编译器的不足之处。当您使用最新的反编译器时,它也应该使用资源再现try。因为这两种结构都只是语法上的甜点。很可能是反编译器在我写下这个答案后的三年半里有所改进:)是的,我认为2比3好(1显然是不可能的)。原因是,在我看来,90%的情况下,异常是针对调试问题的,只有10%是针对捕获问题的。在我看来,拥有更多有价值的调试信息,即使与其他情况不一致,也比拥有更少的调试信息要好得多。@Doublp这一部分是主观的,我同意,而且这三种情况都有很好的理由。我当然不是说3绝对是正确的选择,其他一切都是错误的。我只是想指出这两种结构之间的一个重要区别,这可能会影响决策……例如,你可以说1是最好的选择,因为
    try finally
    在概念上被破坏并修复了它(并且将其与
    try with resources
    保持一致比为了代码而保持向后兼容性更重要,因为代码可能有问题。我认为1是最好的解决方案。我一直认为从
    最后抛出
    是一件坏事,所以打破已经坏掉的代码不是什么大问题“最终在
    中引发异常时注册为抑制”不正确。try with resource构造将注册在
    close()中引发的异常