I have some junit tests which create some resources which should also be closed.
One way to implement this logic is using the @Before
and @After
approach.
What I did was to encapsulate the creation in some utility class to be reused. For example:
class UserCreatorTestUtil implements AutoClosable {
User create() {...}
void close() {...}
}
The whole point is for the object to close itself, rather than needing to remember to close it in @After
.
The usage should be:
@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());
}
}
The problem is that junit's assert keyword throws an Error
- not Exception
.
Will the try-with-resource "catch" the Error
and invoke the close method?
* Couldn't find the answer in the try-with-resources documentation.
It does not catch
anything. But it does finally
close all resources.
finally
blocks are run even when an Error is thrown.
finally
block would do. - ThiloSystem.exit()
: stackoverflow.com/questions/14905006/… - Christophe Roussyfinally
block, as, unlike a finally
block, it will catch all subsequent Throwable
s of the close()
operations and add them as suppressed throwable to the primary throwable. A finally
block can’t do that as it has no knowledage about the primary throwable, if there is one. - Holgerfinally
block in addition to a catch
block that does not really catch anything (just records the primary throwable and rethrows). All the "action" happens in the finally block. See @Nicolas answer for details. - Thilo
The pseudo-code of a basic try-with-resources statement is (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();
}
}
}
As you can see it catches Throwable
not Exception
which includes Error
but only to get the primary exception in order to add as suppressed exceptions any exceptions that occurred while closing the resources.
You can also notice that your resources are closed in the finally
block which means that they will be closed whatever happens (except in case of a System.exit
of course as it terminates the currently running Java Virtual Machine) even in case an Error
or any sub class of Throwable
is thrown.
javac
adheres literally to this convoluted formal specification. Considering, that finally
is eventually implemented via code duplication for the two cases, it makes no sense at all, to store the exception in a variable and test it against null
, when it is already implied by the code path whether there is an exception or not… - Holgerfinally
block is executed whether there was an exception or not. What code duplication do you mean (I guess the two Identifier.close()
) and why does it annoy you? What would you suggest try-with-resources should do instead? - siegifinally
feature, hence, a finally
block is compiled by copying the action to every code path that could leave the block, plus an exception handler, i.e. try { foo(); } finally { bar(); }
gets compiled to try { foo(); } catch(Throwable t) { bar(); throw t; } bar();
, duplicating the bar()
invocation code. There, it is implied by the code path whether an exception occurred or not, i.e. the first bar();
invocation is the one under exceptional condition, the second is the one without. - Holgerjavac
as a consequence of this is discussed in this question. In my answer there, I referred to this behavior as “an artifact of how javac
works internally”, without naming “implements this like a finally
block” directly, simply because I didn’t know that formal specification at this point, but seeing it now, things become entirely clear (and the general statement still holds). - Holgerjavac
but to simply "desugar" it according to the language specification and then let the JIT compiler do its job. - siegi
Try-with-resources don't catch anything in and of themselves.
However, you can attach a catch
block to the end of the try-with-resources block, to catch whatever types of Throwable
you like:
try (UserCreatorTestUtil userCreatorTestUtil = new UserCreatorTestUtil()) {
// ... Whatever
} catch (RuntimeException e) {
// Handle e.
} catch (Exception | Throwable t) {
// Handle t.
}
The idea behind try-with-resources
is to make sure that the resources should be closed.
The problem with conventional try-catch-finally
statements is that let's suppose your try
block throws an exception; now usually you'll handle that exception in finally
block.
Now suppose an exception occurs in finally block as well. In such a case, the exception thrown by try catch is lost and the exception generated in finally
block gets propagated.
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.
}
In try-with-resources
the close()
method of the resource will get automatically called, and if the close()
throws any exception, the rest of the finally
isn't reached, and the original exception is lost.
Contrast that with this:
try (InputStream inputStream= new FileInputStream("C://test.txt")){
// ... use stream
} catch(IOException e) {
// handle exception
}
in the above code snippet, the close()
method automatically gets called and if that close()
method also generated any exception, than that exception will automatically get suppressed.
See also: Java Language Specification 14.20.3
Misconception on your end: try-with-resources does not do a catch.
It does a final finally, therefore the kind of "problem" doesn't matter.
See the JLS for further information!