Good answers have already been posted. I'm just taking a different approach as it feels like an opportunity to dive into some details that may some day be handy, which is trying to answer the question by reading some bytecode.
There are a few scenarios - to look at
- exception in the
try
block
- exception when closing the
auto-closeable
during exiting on the try
block
- exception when
closing
the auto-closeable
resource during handling an earlier exception
- return in the try block, is
close
executed prior to return.
The first scenario is usually top of mind with using try-with
in java. We can try understanding the other three scenarios by looking at the byte code. The last scenario addresses your question.
Breaking down the byte code for the main
method below
import java.io.*;
class TryWith {
public static void main(String[] args) {
try(PrintStream ps = System.out) {
ps.println("Hey Hey");
return;
}
}
}
Lets review it in small parts (some details elided)
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: astore_1
0: get the static field System.out
.
3: store the field into the LocalVariableTable
(lvt) at slot 1.
Reviewing the lvt we can confirm that the first slot is of the java.io.PrintStream
and it has the name ps
LocalVariableTable:
Start Length Slot Name Signature
4 35 1 ps Ljava/io/PrintStream;
0 39 0 args [Ljava/lang/String;
4: aload_1
5: ldc #3 // String Hey Hey
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
4: Load ps
(aload_1
)
5: Load the constant (ldc
), hey hey
from the constant pool.
7: Invoke the print line method, this consumes ps
and hey hey
from the operand stack.
10: aload_1
11: ifnull 18
14: aload_1
15: invokevirtual #5 // Method java/io/PrintStream.close:()V
18: return
10 - 11: load ps
onto the operand stack. check if ps
is null
and if it is null
, jump to 18
and return
from the function.
14 - 18: load ps
, invoke close
and return
.
The above is of particularly interest because it suggest that try-with
block would work if the Auto-Closeable
resource is null
and not throw
an exception. Of course even if it did work, it would be moot - unless the resource wasn't accessed in the try
block. Any access would result in a NPE.
The above is also the normal flow, what happens in the even of an exception? Lets take a look at the exception table
Exception table:
from to target type
4 10 19 Class java/lang/Throwable
24 28 31 Class java/lang/Throwable
This tells us that any exception of type java.lang.Throwable
between byte code 4-10 is handled at target 19. Similarly for lines 24-28 at line 31.
19: astore_2
20: aload_1
21: ifnull 37
24: aload_1
25: invokevirtual #5 // Method java/io/PrintStream.close:()V
28: goto 37
19: Store the exception into local variable 2
.
20 - 25: This is the same pattern we saw earlier close
is only invoked if ps
is not null
28: a jump instruction to 37
37: aload_2
38: athrow
37: load the object stored in the local variable table at position 2, earlier we stored the exception in this position.
38: throw the exception
However what about the case of an exception occurring during close
when the close
was executing on account of an earlier exception. Lets recap the exception table
Exception table:
from to target type
4 10 19 Class java/lang/Throwable
24 28 31 Class java/lang/Throwable
That is the second line the exception table, lets look at the corresponding byte code at target 31
31: astore_3
32: aload_2
33: aload_3
34: invokevirtual #7 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
37: aload_2
38: athrow
31: The secondary exception is stored in the local variable at slot 3.
32: Reload the original exception from slot 3.
33-34: add the secondary exception as the suppressed exception to the original exception.
37-38: throw the new exception, we covered these lines earlier.
Revisiting our consideration listed at the beginning
- exception when closing the
auto-closeable
during exiting on the try
block.
** an exception is raised and the try
block exits abruptly
- exception when
closing
the auto-closeable
resource during handling an earlier exception.
** A suppressed exception is added to the orignal exception and the original exception is thrown. the try
block exits abruptly
- return in the try block, is close executed prior to return.
** close is executed prior to the return
in the try block
Revisiting the interesting scenarios of auto-closeable
resource being null
that we encountered in the byte code, we can test that with
import java.io.*;
class TryWithAnother {
public static void main(String[] args) {
try(PrintStream ps = null) {
System.out.println("Hey Hey");
return;
}
}
}
Not surprisingly we get the output Hey Hey
on the console and no exception.
Last but pretty important to keep in mind is that this bytecode is a compliant implementation of the JLS. This approach is pretty handy to determine what your actual execution entails, there might be other compliant alternatives - in this situation I can't think of any. However with this in mind this response won't be complete without specifying my javac
version
openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.9.1+1)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.9.1+1, mixed mode)
return
flows through the body of thefinally
clause, which is where resources are released. – Selway