Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could a final variable be reassigned in catch, even if assignment is last operation in try?

People also ask

Can a final variable be reassigned?

4. Final Variables. Variables marked as final can't be reassigned. Once a final variable is initialized, it can't be altered.

Can we assign final variable in Java?

In Java, non-static final variables can be assigned a value either in constructor or with the declaration. But, static final variables cannot be assigned value in constructor; they must be assigned a value with their declaration.

How to change value of final variable in Java?

Final variable in Java cannot be changed. Once if we have assigned the final variable it can not be changed it is fixed. but if you have declare a blank final variable then you can assign value to it only in constructor.

When catch block will execute?

When a catch -block is used, the catch -block is executed when any exception is thrown from within the try -block. For example, when the exception occurs in the following code, control transfers to the catch -block.


JLS hunting:

It is a compile-time error if a final variable is assigned to unless it is definitely unassigned (§16) immediately prior to the assignment.

Quoth chapter 16:

V is definitely unassigned before a catch block iff all of the following conditions hold:

V is definitely unassigned after the try block.
V is definitely unassigned before every return statement that belongs to the try block.
V is definitely unassigned after e in every statement of the form throw e that belongs to the try block.
V is definitely unassigned after every assert statement that occurs in the try block.
V is definitely unassigned before every break statement that belongs to the try block and whose break target contains (or is) the try statement.
V is definitely unassigned before every continue statement that belongs to the try block and whose continue target contains the try statement.

Bold is mine. After the try block it is unclear whether i is assigned.

Furthermore in the example

final int i;
try {
    i = foo();
    bar();
}
catch(Exception e) { // e might come from bar
    i = 1;
}

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

V is definitely unassigned after the try block, if the catch block catches an unchecked exception.
V is definitely unassigned before the last statement capable of throwing an exception of a type caught by the catch block, if the catch block catches an unchecked exception.

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.


I think the JVM is, sadly, correct. While intuitively correct from looking at the code, it makes sense in the context of looking at the IL. I created a simple run() method that mostly mimics your case (simplified comments here):

0: aload_0
1: invokevirtual  #5; // calculateIndex
4: istore_1
5: goto  17
// here's the catch block
17: // is after the catch

So, while you can't easily write code to test this, because it won't compile, the invoke of the method, the store the value, and the skip to after the catch are three separate operations. You could (however unlikely that may be) have an exception occur (Thread.interrupt() seems to be the best example) between step 4 and step 5. This would result in entering into the catch block after i has been set.

I'm not sure you could intentionally make that happen with a ton of threads and interrupts (and the compiler won't let you write that code anyway), but it is thus theoretically possible that i could be set, and you could enter in the exception handling block, even with this simple code.


Not quite as clean (and I suspect what you are already doing). But this only adds 1 extra line.

final int i;
int temp;
try { temp = calculateIndex(); }
catch (IOException e) { temp = 1; }
i = temp;

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.


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:

try {
    foo = bar();
    if (foo) {
        i = 4;
    } else {
        i = 7;
    }
}

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 the Error, in the same or a surrounding try statement). For instance, consider:

final int i;
try {
    try {
        i = foo();
    } catch (Exception e) {
        bar();
        i = 1;
    }
} catch (Throwable t) {
    i = 0;
}

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:

final int i = calculateIndex();

and

int calculateIndex() {
    try {
        // calculate it
        return calculatedIndex;
    } catch (Exception e) {
        return 0;
    }
}

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.