最后使用时抑制的异常消失了吗?

Suppressed exception disappeared when using finally?

这是代码。

public class TestTest {
    public static void main (String[] args) throws Exception {
        try {
            run();
        } catch(Exception e) {
            printSuppressedExceptions(e);
        }
    }

    public static void printSuppressedExceptions(Throwable t) {
        System.out.println(t);
        System.out.println("suppressed exceptions: " + t.getSuppressed().length);
    }

    public static void run() throws Exception {
        try(MyResource r = new MyResource("resource");) {
            System.out.println("try");
            System.getProperty("").length(); // throws illegalArgumentException
        } catch(Exception e) {
            printSuppressedExceptions(e);
            throw e;
        } finally {
            new MyResource("finally").close();
        }
    }   
}

class MyResource implements AutoCloseable {
    private final String name;

    public MyResource(String name) {
        this.name = name;
    }

    @Override
    public void close() throws Exception {
        throw new Exception("exception" + " from " + this.name);
    }
}    

由于 try 块抛出的异常抑制了资源的异常,所以我一开始得到 "suppressed exceptions: 1",这是可以理解的。但是当最后抛出异常时,似乎所有被抑制的异常都消失了,因为我得到 "java.lang.Exception: exception from finally" 后跟 "suppressed exceptions: 0",我认为它应该是 1。 我浏览了 Java 教程,它肯定是

However, in this example, if the methods readLine and close both throw exceptions, then the method readFirstLineFromFileWithFinallyBlock throws the exception thrown from the finally block; the exception thrown from the try block is suppressed.

来自The try-with-resources Statement

怎么会这样?

下面的代码可以满足您的预期:

public class TestTest {
    public static void main (String[] args) throws Exception {
        try {
            run();
        } catch(Exception e) {
            printSuppressedExceptions(e);
        }
    }

    public static void printSuppressedExceptions(Throwable t) {
        System.out.println(t);
        System.out.println("suppressed exceptions (" + t.getSuppressed().length + "):");
        for (Throwable suppressed : t.getSuppressed()) {
            System.out.println("  - " + suppressed);
        }
    }

    public static void run() throws Exception {
        Exception exceptionFromCatch = null;
        try(MyResource r = new MyResource("resource");) {
            System.out.println("try");
            System.getProperty("").length(); // throws illegalArgumentException
        } catch(Exception e) {
            exceptionFromCatch = e;
            printSuppressedExceptions(e);
            throw e;
        } finally {
            try {
                new MyResource("finally").close();
            } catch (Exception e) {
                if (exceptionFromCatch!=null) {
                    e.addSuppressed(exceptionFromCatch);
                }
                throw e;
            }
        }
    }   
}

class MyResource implements AutoCloseable {
    private final String name;

    public MyResource(String name) {
        this.name = name;
    }

    @Override
    public void close() throws Exception {
        throw new Exception("exception" + " from " + this.name);
    }
}

所以让我们通过代码的 try-with-resource 部分(在 JDK 1.7.0 中引入)看看会发生什么(请参阅 What is the Java 7 try-with-resources bytecode equivalent using try-catch-finally? 了解更多详情):

  • try-with-resource 块 MyResource r = new MyResource("resource") 已执行
  • 执行 try 块并抛出 IllegalArgumentException
  • try-with-resource 块为所有资源调用 close()(在您的示例中只有一个)
  • close() 抛出异常,但由于 try 块中的异常具有优先级,因此 close() 抛出的异常被抑制并通过 addSuppressed(..)
  • 添加

因此该部分的工作方式与您阅读本教程时预期的一样。

现在是代码的 try-catch-finally 部分(如 JDK 1.6 及更早版本):

  • 执行 try 块并抛出 IllegalArgumentException
  • (catch 块的行为与没有 catch 块的行为相同)
  • 执行 finally 块并抛出异常
  • 来自 finally 块的异常优先,来自 try 块的异常被抑制

但是这次suppressed这个词在java教程中并不代表"suppressed and added to the actually thrown exception""suppressed and lost to nirvana"。所以它的行为仍然与 JDK 1.6 及更早版本中的一样,并且不使用新引入的 addSuppressed(..) getSuppressed() 功能。这就是它不像您预期​​的那样运行的原因。


我认为您预期的行为也不符合逻辑。我希望它表现得像这样:

...
        } finally {
            try {
                new MyResource("finally").close();
            } catch (Exception e) {
                if (exceptionFromCatch!=null) {
                    exceptionFromCatch.addSuppressed(e);
                } else {
                    throw e;
                }
            }
        }
...

这将始终优先考虑 try 块中的异常(使用新的 try-with-resource 功能实现)并将 catch 块中的异常添加到列表中。但这会破坏与 JDK 1.6 的兼容性,所以我想这就是它不那样做的原因。