Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8341783

Allow lambda capture of basic for() loop variables as with enhanced for()

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Withdrawn
    • Icon: P4 P4
    • 24
    • tools
    • None
    • source
    • low
    • The compiler's behavior will only differ for source files that previously did not compile. Therefore, there should not be any actual runtime effect. However, any test cases, etc., that rely on the older more restrictive rules will need to be updated.
    • Language construct
    • SE

      Summary

      Modify the requirement that variables captured by lambdas, nested classes, and switch case guards be final or effectively final in the specific case of a basic for() loop variable captured within the body of that loop. The modification will allow such variables to be effectively final in the body of the loop, a new term defined herein which is equivalent to the current rules for enhanced for() loops.

      Problem

      Java requires a variable captured by lambdas, nested classes, and switch case guards to be final or effectively final: since the variable appears in different scopes that will execute at different times, this avoids ambiguity about the variable's possible value.

      However, in the bodies of for() loops (both basic and enhanced) there is no ambiguity about loop variables' possible values: the value of a loop variable in the body of a for() loop is whatever value is computed by the header of the loop for that particular iteration of the loop; this true is by definition.

      However, the rules for variable capture only take advantage of the special structure of for() loops in the case of enhanced for() loops but not basic for() loops. The result is that this code compiles:

      for (int i : new int[] { 1, 2, 3 }) {
          Runnable r = () -> System.out.println(i); // ok

      but this equivalent code does not:

      for (int i = 1; i <= 3; i++) {
          Runnable r = () -> System.out.println(i); // error

      As the enhanced for() loop example shows, what's needed to avoid ambiguity when capturing loop variables within for() loop bodies is not that the variable be effectively final, but that it be effectively final in the body of the loop.

      In the enhanced for() example, i is neither final nor effectively final, so the usual rules for capturing i in the lambda would prevent this from compiling. But that behavior would be too restrictive because there's no ambiguity in this context, and therefore the Java language specifies that i may be captured as long as it is not reassigned in the body of the loop. It does this by specifying that, for the purpose of determining whether capture is allowed, such a loop should be evaluated as if it were written like this:

      for (int i : new int[] { 1, 2, 3 }) {
          int i$copy = i;
          Runnable r = () -> System.out.println(i);
      }

      This makes sense because each iteration of the loop is an independent action with a new value for the loop variable, and so the scope over which the loop variable needs to be effectively final in order to be unambiguously captured is the body of the loop, not the entire loop. We are calling this property of a for() loop variable effectively final in the body of the loop.

      However, because basic for() loops were included in the original Java language prior to the introduction of lambdas, they were never granted this same benefit. Regardless, the same logic for variable capture applies: there is no ambiguity about loop variable values in the body of the loop, so they should be allowed to be captured under the same conditions as in enhanced for() loops, namely, as long as they are effectively final in the body of the loop. Therefore, basic for() loop variables should also be allowed to be captured within the body of the loop under the same conditions.

      Solution

      For the purpose of whether to allow loop variable capture, basic for() loops that look like this:

      for (int i = 1; i <= 3; i++)
          //...statements involving i...

      would be treated as if they actually looked like this:

      for (int i = 1; i <= 3; i++) {
          int i$ = i;
          //...statements involving i$...
      }

      This is the same criteria as specified for enhanced for() loops. However, in the specification we can't just copy and paste it, because enhanced for() loops are actually defined in terms of basic for() loops.

      Instead, we first define a new phrase effectively final in the body of the loop that applies to basic for() loop variables, i.e., variables declared in the header of the loop.

      We then define the phrase effectively final for E, where E is any Java construct, to mean either effectively final, or effectively final in the body of the loop when E is contained in the loop.

      Finally we alter the relevant requirements in the specification that a variable be "effectively final" to be "effectively final for E" where E is the particular construct under discussion.

      Specification

      At the end of §4.12.4 (final Variables), add:

      If a basic for statement (§14.14.1) F has initialization part I, condition part C, and statement part S, and I declares local variable V, then V is effectively final in the body of F if all of the following are true:

      • V is not declared final
      • Under an assumption that V is definitely [un]assigned before S iff V is definitely [un]assigned after C (§16.2.12), then whenever V occurs in S as the left hand side in an assignment expression, it would be definitely unassigned and not definitely assigned before the assignment; that is, it would be definitely unassigned and not definitely assigned after the right hand side of the assignment expression
      • Within S, V never occurs as the operand of a prefix or postfix increment or decrement operator.

      [New Non-Normative Note] A basic for() loop variable is effectively final in the body of the loop if it would be effectively final when one ignored any changes to the variable in the header of the loop.

      For any variable V and construct E, V is effectively final for E if either of the following is true:

      • V is effectively final
      • V is effectively final in the body of a basic for statement with statement part S, and E is contained within S

      Update §6.5.6.1 (Simple Expression Names):

      If the declaration denotes a local variable, formal parameter, or exception parameter that is neither final nor effectively final for the expression name (§4.12.4), it is a compile-time error...

      Update §8.1.3 (Inner Classes and Enclosing Instances):

      Any local variable, formal parameter, or exception parameter used but not declared in an inner class must either be final or effectively final for the inner class (§4.12.4), as specified in §6.5.6.1.

      Update §14.11.1 (Switch Blocks):

      A guard associated with a case label must satisfy all of the following conditions, otherwise a compile-time error occurs...

      • Any local variable, formal parameter, or exception parameter used but not declared in a guard must either be final or effectively final for the guard (§4.12.4).

      Update §14.20.3 (try-with-resources):

      • An existing variable referred to in a resource specification must be final or effectively final for the resource specification (§4.12.4), or a compile-time error occurs.
      • An existing variable referred to in a resource specification must be definitely assigned before the try-with-resources statement (§16 (Definite Assignment)), or a compile-time error occurs.

      Update §15.27.2 (Lambda Body):

      Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be final or effectively final for the labmda expression (§4.12.4), as specified in §6.5.6.1.

      [Non-Normative Note] The restriction to effectively final variables includes standard loop variables, but not basic-for and enhanced-for loop variables, which are treated as distinct for each iteration of the loop (§4.12.4, §14.14.2).

      Update code example m10() - it no longer generates an error.

            acobbs Archie Cobbs
            acobbs Archie Cobbs
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved: