简体   繁体   中英

Assignment of effectively final variable in try/catch statement

The following code does not compile with javac 1.8.0_144 and ecj:

private LongSupplier foo() {
    long fileSize;
    try {
        fileSize = canThrow();
    } catch (IOException e) {
        fileSize = 42;
    }

    LongSupplier foo = () -> 1 + fileSize;
    return foo;
}

I am wondering if this a bug in the compiler. The definition of effectively final in the JLS is:

Certain variables that are not declared final are instead considered effectively final:

  • A local variable whose declarator has an initializer (§14.4.2) is effectively final if all of the following are true:

    • It is not declared final.

    • It never occurs as the left hand side in an assignment expression (§15.26). (Note that the local variable declarator containing the
      initializer is not an assignment expression.)

    • It never occurs as the operand of a prefix or postfix increment or decrement operator (§15.14, §15.15).

  • A local variable whose declarator lacks an initializer is effectively final if all of the following are true:

    • It is not declared final.

    • Whenever it occurs as the left hand side in an assignment expression, it is definitely unassigned and not definitely assigned before the assignment; that is, it is definitely unassigned and not definitely assigned after the right hand side of the assignment expression (§16 (Definite Assignment)).

    • It never occurs as the operand of a prefix or postfix increment or decrement operator.

  • A method, constructor, lambda, or exception parameter (§8.4.1, §8.8.1, §9.4, §15.27.1, §14.20) is treated, for the purpose of
    determining whether it is effectively final, as a local variable
    whose declarator has an initializer.

My reading is that in clause 2, the assignments in the try/catch block are allowed because fileSize is definitely unassigned before the assignment.

I think the reasoning to explain the rejection of the code is:

  • fileSize is definitely unassigned before the try block
  • fileSize is assigned (definitely? It seems that 16.1.8 does not care about exceptions in the assignment) after fileSize = canThrow()
  • fileSize is assigned after the try block
  • fileSize is not definitely unassigned before the catch block, and thus not definitely unassigned before the assignment in the catch block.
  • thus, clause 2 of 4.12.4 does not apply here

Is this correct?

The definition of "Effectively final" states that adding a final modifier should not change anything. Let's do that and get a clearer error:

error: variable fileSize might already have been assigned
                    fileSize = 42;
                    ^

So this is the exact same case as Final variable assignment with try/catch (which also gives a workaround using a second final variable), namely the variable appears on the left of an assignment, which means it is not definitely unassigned.

(For good order try-catch has nothing to do with the issue: they merely argue that the catch exception parameter is considered final.)

The intention of being "effectively final" is founded on there being two threads with each a copy of the identically named variable. The life times of those two threads/variables are different. They want to prevent change in one thread which would have needed some synchronisation and lifeliness check.

So they definitely do not want an assignment. As language design decision.

Indeed an internal thread in canThrow could use fileSize being still 0 after the catch set the other variable fileSize to 42. I think you consider a raised exception to signify the other thread is dead.

What you want in this case is a Future/FutureTask or such.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM