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

前端 未结 12 1129
粉色の甜心
粉色の甜心 2020-12-04 18:49

I am quite convinced that here

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

i cannot possibly have

相关标签:
12条回答
  • 2020-12-04 19:25

    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"

    0 讨论(0)
  • 2020-12-04 19:35

    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.

    0 讨论(0)
  • 2020-12-04 19:39
    1   final int i;
    2   try { i = calculateIndex(); }
    3   catch (Exception e) { 
    4       i = 1; 
    5   }
    

    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

    0 讨论(0)
  • 2020-12-04 19:41

    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;
    
    0 讨论(0)
  • 2020-12-04 19:43

    Browsing the javadoc, it seems no subclass of Exception could be thrown just after i is assigned. From a JLS theoretical perspective, it seems Error could be thrown just after i is assigned (e.g. VirtualMachineError).

    Seems there's no JLS requirement for compiler to determine whether i could be previously set when catch block is reached, by distinguishing whether you're catching Exception or Error/Throwable, implying it's a weakness of JLS model.

    Why not try the following? (have compiled & tested)

    (Integer Wrapper Type + finally + "Elvis" operator to test whether null):

    import myUtils.ExpressionUtil;
    ....
    Integer i0 = null; 
    final int i;
    try { i0 = calculateIndex(); }   // method may return int - autoboxed to Integer!
    catch (Exception e) {} 
    finally { i = nvl(i0,1); }       
    
    
    package myUtils;
    class ExpressionUtil {
        // Custom-made, because shorthand Elvis operator left out of Java 7
        Integer nvl(Integer i0, Integer i1) { return (i0 == null) ? i1 : i0;}
    }
    
    0 讨论(0)
  • 2020-12-04 19:44

    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.

    0 讨论(0)
提交回复
热议问题