I am quite convinced that here
final int i;
try { i = calculateIndex(); }
catch (Exception e) { i = 1; }
i
cannot possibly have already been assigned if control reaches the catch-block. However, Java compiler disagrees and claims the final local variable i may already have been assigned
.
Is there still some subtlety I am missing here, or is this just a weakness of the model used by the Java Language Specification to identify potential reassignments? My main worry are things like Thread.stop()
, which may result in an exception being thrown "out of thin air," but I still don't see how it could be thrown after the assignment, which is apparently the very last action within the try-block.
The idiom above, if allowed, would make many of my methods simpler. Note that this use case has first-class support in languages, such as Scala, which consistently employ the Maybe monad:
final int i = calculateIndex().getOrElse(1);
I think this use case serves as a quite good motivation to allow that one special case where i
is definitely unassigned within the catch-block.
UPDATE
After some thought I am even more certain that this is just a weakness of the JLS model: if I declare the axiom "in the presented example, i
is definitely unassigned when control reaches the catch-block", it will not conflict with any other axiom or theorem. The compiler will not allow any reading of i
before it is assigned in the catch-block, so the fact whether i
has been assigned to or not cannot be observed.
As per specs JLS hunting done by "djechlin", specs tells when is the variable definitely unassigned. So spec says that in those scenarios it is safe to allow the assignment.There can be scenarios other than the one mentioned in the specs in which case variable can still be unassigned and it will depend on compiler to make that intelligent decision if it can detect and allow an assignment.
Spec in no way mentions in the scenario specified by you, that compiler should flag an error. So it depends on compiler implementation of spec if it is intelligent enough to detect such scenarios.
Reference: Java Language Specification Definite Assignment section "16.2.15 try Statements"
This is a summary of the strongest arguments in favor of the thesis that the current rules for definite assignment cannot be relaxed without breaking consistency (A), followed by my counterarguments (B):
A: on the bytecode level the write to the variable is not the last instruction within the try-block: for example, the last instruction will typically be a
goto
jump over the exception handling code;B: but if the rules state that
i
is definitely unassigned within the catch-block, its value may not be observed. An unobservable value is as good as no value;A: even if the compiler declares
i
as definitely unassigned, a debug tool could still see the value;B: in fact, a debug tool could always access an uninitialized local variable, which will on a typical implementation have any arbitrary value. There is no essential difference between an uninitialized variable and a variable whose initialization completed abruptly after the actual write having occurred. Regardless of the special case under consideration here, the tool must always use additional metadata to know for each local variable the range of instructions where that variable is definitely assigned and only allow its value to be observed while execution finds itself within the range.
Final Conclusion:
The specification could consistently receive more fine-grained rules which would allow my posted example to compile.
JLS hunting:
Quoth chapter 16:
Bold is mine. After the
try
block it is unclear whetheri
is assigned.Furthermore in the example
The bold text is the only condition preventing the actual erroneous assignment
i=1
from being illegal. So this is sufficient to prove that a finer condition of "definitely unassigned" is necessary to allow the code in your original post.If the spec were revised to replace this condition with
Then I believe your code would be legal. (To the best of my ad-hoc analysis.)
I submitted a JSR for this, which I expect to be ignored but I was curious to see how these are handled. Technically fax number is a required field, I hope it won't do too much damage if I entered +1-000-000-000 there.
You are correct that if the assignment is the very last operation in the try block, we know that upon entering the catch block the variable will not have been assigned. However, formalizing the notion of "very last operation" would add significant complexity to the spec. Consider:
Would that feature be useful? I don't think so, because a final variable must be assigned exactly once, not at most once. In your case, the variable would be unassigned if an
Error
is thrown. You may not care about that if the variable runs out of scope anyway, but such is not always the case (there could be another catch block catching theError
, in the same or a surrounding try statement). For instance, consider:That is correct, but wouldn't be if the call to bar() occured after assigning i (such as in the finally clause), or we use a try-with-resources statement with a resource whose close method throws an exception.
Accounting for that would add even more complexity to the spec.
Finally, there is a simple work around:
and
that makes it obvious that i is assigned.
In short, I think that adding this feature would add significant complexity to the spec for little benefit.
I think there is one situation where this model act as life saver. Consider the code given below:
Now Consider the line (1). Most of the JIT compilers creates object in following sequence(psuedo code):
But, some JIT compilers does out of order writes. And above steps is reordered as follows:
Now suppose, the
JIT
performsout of order writes
while creating the object in line (1). And suppose an exception is thrown while executing the constructor. In that case, thecatch
block will havei
which isnot null
. If JVM doesn't follow this modal then in this case final variable is allowed to be assigned twice!!!OP already remarks that at line 4 i may already have been assigned. For example through Thread.stop(), which is an asynchronous exception, see http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5
Now set a breakpoint at line 4 and you can observe the state of the variable i before 1 is assignd. So loosening the observed behaviour would go against the Java™ Virtual Machine Tool Interface