【问题标题】:Does Java's try-with-resources catch errors or just exceptions?Java 的 try-with-resources 是捕获错误还是捕获异常?
【发布时间】:2017-03-15 17:26:42
【问题描述】:

我有一些 junit 测试,它们创建了一些也应该关闭的资源。

实现此逻辑的一种方法是使用@Before@After 方法。

我所做的是将创建封装在某个实用程序类中以供重用。例如:

class UserCreatorTestUtil implements AutoClosable {
  User create() {...}
  void close() {...}
}

重点是对象自己关闭,而不是需要记住在@After中关闭它。

用法应该是:

@Test
void test() {
  try (UserCreatorTestUtil userCreatorTestUtil = new UserCreatorTestUtil()) {
    User user = userCreatorTestUtil.create();
    // Do some stuff regarding the user's phone
    Assert.assertEquals("123456789", user.getPhone());
  }
}

问题是 junit 的 assert 关键字抛出 Error - 而不是 Exception

try-with-resource 会“捕获”Error 并调用 close 方法吗?

* 在try-with-resources documentation 中找不到答案。

【问题讨论】:

  • OT:仅作记录:“我有一些 junit 测试会创建一些资源” 所以你没有 UnitTests,你有使用 UnitTest - FrameworkModule Tests
  • @TimothyTruckle - 在挑选 nits 时:一些资源 也可以指代一个模拟版本,但仍然必须关闭它才能使一切有意义。
  • 错误和异常(大写 E)都是异常的种类(小写 e)。
  • @immibis 但我们可以称它们为 Throwables 以区分大小写且明确。
  • @Thilo 但不区分大小写的语言简直就是撒旦。

标签: java exception junit exception-handling


【解决方案1】:

它没有catch 任何东西。但它确实 finally 关闭了所有资源。

finally 阻止are run even when an Error is thrown

【讨论】:

  • 不,它根本不知道任何异常/错误。
  • 是的,它以与手动 finally 块相同的方式进行清理。
  • 请注意,除了System.exit()stackoverflow.com/questions/14905006/…
  • 它的行为几乎类似于finally 块,因为与finally 块不同,它会捕获close() 操作的所有后续Throwables 和将它们作为 suppressed throwable 添加到主要 throwable 中。 finally 块无法做到这一点,因为它不了解主要可投掷物(如果有的话)。
  • @Holger:嗯,它被实现为一个finally除了 一个catch 块,它并没有真正捕获任何东西(只记录主要的 throwable 和重新抛出)。所有的“动作”都发生在 finally 块中。有关详细信息,请参阅@Nicolas 答案。
【解决方案2】:

基本 try-with-resources 语句的伪代码是 (cf Java Language Specification §14.20.3.1):

final VariableModifiers_minus_final R Identifier = Expression;
Throwable #primaryExc = null;

try ResourceSpecification_tail
    Block
catch (Throwable #t) {
    #primaryExc = #t;
    throw #t;
} finally {
    if (Identifier != null) {
        if (#primaryExc != null) {
            try {
                Identifier.close();
            } catch (Throwable #suppressedExc) {
                #primaryExc.addSuppressed(#suppressedExc);
            }
        } else {
            Identifier.close();
        }
    }
}

如您所见,它捕获 Throwable 而不是 Exception,其中包括 Error仅用于获取主要异常,以便将关闭资源时发生的任何异常添加为抑制异常.

您还可以注意到,您的资源在 finally 块中已关闭,这意味着 无论发生什么都将关闭(当然,System.exit 除外,因为它终止了当前运行 Java 虚拟机),即使抛出 ErrorThrowable 的任何子类。

【讨论】:

  • 不幸的是,javac 严格遵守这个复杂的正式规范。考虑到finally 最终是通过代码复制来实现这两种情况的,将异常存储在变量中并针对null 进行测试是没有意义的,因为代码路径已经暗示了是否存在是不是例外……
  • @Holger: 代码路径暗示是否存在异常not。请记住,无论是否存在异常,都会执行 finally 块。你的意思是什么代码重复(我猜是两个Identifier.close()),为什么它会惹恼你?您建议 try-with-resources 应该做什么?
  • @siegi:在字节码级别上,没有finally 功能,因此,finally 块是通过将操作复制到可能离开块的每个代码路径来编译的,加上一个异常处理程序,即try { foo(); } finally { bar(); } 被编译为try { foo(); } catch(Throwable t) { bar(); throw t; } bar();,复制bar() 调用代码。在那里,代码路径暗示是否发生异常,即第一个 bar(); 调用是在异常条件下的,第二个是没有的。
  • @siegi:javac 因此生成的代码在this question 中讨论。在我的回答中,我将这种行为称为“javac 如何在内部工作的工件”,而没有直接命名“像finally 块一样实现它”,只是因为我此时不知道正式规范,但现在看到它,事情变得完全清楚(并且一般性陈述仍然成立)。
  • @Holger:哦,我想到了 Java 语言级别的代码重复(即上面的片段),而不是字节码级别。我想这是一个设计决定,在 javac 中对 try-with-resources 没有特殊情况处理,而是根据语言规范简单地“脱糖”,然后让 JIT 编译器完成它的工作。
【解决方案3】:

Try-with-resources 本身不会捕获任何东西。

但是,您可以将 catch 块附加到 try-with-resources 块的末尾,以捕获您喜欢的任何类型的 Throwable

try (UserCreatorTestUtil userCreatorTestUtil = new UserCreatorTestUtil()) {
  // ... Whatever
} catch (RuntimeException e) {
  // Handle e.
} catch (Exception | Throwable t) {
  // Handle t.
}

【讨论】:

    【解决方案4】:

    try-with-resources 背后的想法是确保资源应该被关闭。

    传统try-catch-finally 语句的问题在于,假设您的try 块抛出异常;现在通常您将在finally 块中处理该异常。

    现在假设 finally 块中也发生了异常。在这种情况下,try catch 抛出的异常丢失finally 块中产生的异常被传播。

    try {
        // use something that's using resource
        // e.g., streams
    } catch(IOException e) {
       // handle 
    } finally {
        stream.close();
        //if any exception occurs in the above line, than that exception
        //will be propagated and the original exception that occurred
        //in try block is lost.
    }
    

    try-with-resources中,资源的close()方法会被自动调用,如果close()抛出任何异常,finally的其余部分都达不到,原来的异常就丢失了。

    对比一下:

    try (InputStream inputStream= new FileInputStream("C://test.txt")){
        // ... use stream
    } catch(IOException e) {
       // handle exception
    }
    

    在上面的代码 sn-p 中,close() 方法会自动被调用,如果 close() 方法也产生任何异常,那么该异常将自动被抑制。

    另请参阅:Java Language Specification 14.20.3

    【讨论】:

      【解决方案5】:

      你的误解:try-with-resources 做一个catch

      它做了一个final finally,因此“问题”的种类并不重要。

      请参阅JLS 了解更多信息!

      【讨论】:

      • 但它确实做了一个catch,因此它可以保留原始异常(如果有),并将后续异常添加为抑制异常。
      猜你喜欢
      • 2021-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-14
      • 2015-06-21
      • 2011-05-27
      相关资源
      最近更新 更多