Here are some alternatives titles I could’ve used:
Today I’m going to briefly discuss something that tripped me up in my day job the other day. This is perhaps something that every Java developer is taught during the first day on the job or in class but which no one ever remembers until the day it bites them in the behind. Anyway, it’s something I didn’t know and is really scary. I point you first at the Java Language Specification (JLS), specifically the section 14.20.2. Execution of try
-finally
and try
-catch
-finally
. I will caution that everything in this section of the document seems completely obvious at first glance. However, I would like to bring your attention to two specific sentences:
If the
finally
block completes abruptly for reasonS
, then thetry
statement completes abruptly for reasonS
(and reasonR
is discarded).
and
If the
finally
block completes abruptly for reasonS
, then thetry
statement completes abruptly for reasonS
(and thethrow
of valueV
is discarded and forgotten).
Note that these two sentences are very similar and have similar implications. In the version of the JLS I’m looking at, the second sentence is specifically rendered in bold. I would argue that the first sentence should be similarly rendered in bold for the same reason: these are both counterintuitive behaviours that can have real-world consequences.
Consider the following pseudo-Java:
This would seem to be a reasonable way to achieve the following:
However, the Java specification tells us that if the try
block throws an exception that is assignment-compatible with Exception
(in the declaration of the catch
block), the catch
block will run followed by the finally
block and, most importantly for our purposes here, the throw at the end of the catch
block will be discarded and replaced by the return true
at the end of the finally
block.
Let that sink in for a minute. Go back and re-read that. This may not ring alarm bells in your head immediately, but it freaked me out. You may encounter code like this yourself: code that assumes that the exception will be rethrown. This is what a percursory glance at the code would suggest. The return true
is an example of what I’m going to call “action at a distance” and means that, in order to fully understand any given instance of the try
-catch
-finally
construct in Java, you have to consider how the finally
block interacts with both your try
and catch
blocks. In summary, neither try
nor catch
blocks are fully self-describing.
The key to interpreting the JLS description of finally
blocks is to understand what it means for a statement to complete abruptly. This is described in detail in the section 14.1. Normal and Abrupt Completion of Statements in the JLS. This section enumerates all of the associated reasons for an abrupt completion in Java, three of which are relevant to our example:
- A
return
with no value- A
return
with a given value- A
throw
with a given value, including exceptions thrown by the Java Virtual Machine
Thus, in terms of the terminology used in the JLS:
try
block runs and completes abruptly due to a throw
of value V
(where V
is an exception of type RuntimeException
)RuntimeException
is assignment-compatible with Exception
as per the declaration of the catch
block, the catch
block runs and completes abruptly for reason R
(i.e. throw e
)finally
block completes abruptly for reason S
(i.e. return true
)The overall result, therefore, is that the whole try
statement completes abruptly with reason S
, i.e. return true
, with reason R
, i.e. throw e
, being ignored.
The “obvious” behaviour (i.e. the exception is caught and rethrown and the clean-up code in finally
block is run) can be achieved by rewriting this code as follows:
Critically, the finally
block in this example does not complete abruptly and, consequently, the try
block completes with reason R
(i.e. throw e
) and the overall statement throws the exception.
This following program systematically demonstrates these behaviours:
This program will generate the following output:
So, beware.
Content © 2024 Richard Cook. All rights reserved.