2

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?

6
  • I would say that fileSize is not effectively final because it can be altered after fileSize = 0 exception or otherwise. i.e. it wouldn't compile if you make the variable final Commented Dec 22, 2017 at 14:11
  • @PeterLawrey The problem is that javac accepts the code alwthough IMHO it shouldn't. Neither of the clauses of the definition apply here, so it should be rejected, although I don't see a technical problem why it cannot be done when the variable is not assigned after the lambda declaration. When the initiaztion is removed, javac still accepts the code (which is correct IMHO), but ecj still rejects it. Commented Dec 22, 2017 at 14:23
  • I agree that the behaviour is reasonable though not to specification which means it could break in the future. Commented Dec 22, 2017 at 14:26
  • 2
    Sorry guys, I compiled the wrong file. Both ecj and javac reject the code, but I am not sure if this is compliant with the JLS. Commented Dec 22, 2017 at 14:40
  • 1
    stackoverflow.com/questions/13604111/… Commented Dec 22, 2017 at 15:26

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

2

(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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.