Accessing Local Variables in the Enclosing Context
All variable declarations in a lambda expression follow the rules of block scope. They are not accessible outside the lambda expression. It also means that we cannot redeclare local variables already declared in the enclosing scope. In Example 13.2, redeclaring the parameter banner and the local variable words at (6) and (7), respectively, in the body of the lambda expression results in a compile-time error. Local variables declared in the enclosing method, including its formal parameters, can be accessed in a lambda expression provided they are effectively final. This means that once a local variable has been assigned a value to the time when it is used, its value has not changed during that time. Using the final modifier in the declaration of a local variable explicitly instructs the compiler to ensure that this is the case. The final modifier implies effectively final. If the final modifier is omitted and a local variable is used in a lambda expression, the compiler effectively performs the same analysis as if the final modifier had been specified.
A lambda expression may be executed at a later time, after the method in which it is defined has finished execution. At that point, the local variables in its enclosing context that are used in the lambda expression are no longer accessible. To ensure their availability, copies of their values are maintained with the lambda expression. This is called variable capture, although in essence it is the values that are captured. Note that it is not the object that is copied in the case of a local reference variable, but the reference value. Objects reside on the heap and are accessible via a copy of the reference value. Correct execution of the lambda expression is guaranteed, since these effectively final values cannot change. Note that the state of an object referred to by a final or an effectively final reference can change, but not the reference value stored in the reference—that is, such a reference will continue to refer to the same object once it is initialized.
In Example 13.2, the method getPredicate() at (1) has one formal parameter (banner), and a local variable (words) declared at (2). Although the state of the Array-List<String> object, referred to by the reference words, is changed in the method (we add elements to it), the reference value in the reference does not—that is, it continues to refer to the same object whose reference value it was assigned at (2). The parameter banner is assigned the reference value of the argument object when the method is invoked, and continues to refer to this object throughout the method. Both local variables are effectively final. Their values are captured by the lambda expression, and used when the lambda expression is executed at a later time after the call to the getPredicate() method in the main() method has completed.
However, if we uncomment (3) and (4) in Example 13.2, then both local variables are not effectively final. Their reference values are changed at (3) and (4), respectively. The compiler now flags an error at (8) and (9), respectively because these non-final local variables are used in the lambda expression.
The vigilant reader no doubt will have noticed that no requirement of effectively final was imposed on the field members of the enclosing class or object, when accessed in the lambda body. Reference to the enclosing object or class is captured by the lambda expression, and when the expression is executed at a later time, the reference can readily be used to access values of any fields referenced in the lambda body—no copies of such values need be made, thereby making the effectively final rule unnecessary for accessing fields in the enclosing context.
Example 13.2 Accessing Local Variables in the Enclosing Method
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class LocalsOnly {
public static void main(String[] args) {
StringBuilder banner = new StringBuilder(“love “);
LocalsOnly instance = new LocalsOnly();
Predicate<String> p = instance.getPredicate(banner);
System.out.println(p.test(“never dies!”) + ” ” + banner);
}
public Predicate<String> getPredicate(StringBuilder banner) { // (1)
List<String> words = new ArrayList<>(); // (2)
words.add(“Tom”); words.add(“Dick”); words.add(“Harriet”);
// banner = new StringBuilder(); // (3) Illegal: Not effectively final
// words = new ArrayList<>(); // (4) Illegal: Not effectively final
return str -> { // (5) Lambda expression
// String banner = “Don’t redeclare me!”; // (6) Illegal: Redeclared
// String[] words = new String[6]; // (7) Illegal: Redeclared
System.out.println(“List: ” + words); // (8) Local variable
banner.append(str); // (9) Parameter
return str.length() > 5;
};
}
}
Output from the program:
List: [Tom, Dick, Harriet]
true love never dies!