Borrowing variables: the effectively final rule¶
☕ Java 17 · How Lambdas See the Variables Around Them · 3/4
A lambda can use variables from the class or method around it, but not all of them on the same terms.
| Variable | Can the lambda use it? |
|---|---|
| Instance and static fields | ✅ always, and it can even change them |
| Method parameters | ✅ only if final or effectively final |
| Local variables | ✅ only if final or effectively final |
Effectively final¶
A variable is effectively final when it's assigned once and never reassigned, before or after the lambda, even without the final keyword.
Quick test: add final in front of it in your head. If the code still compiles, it's effectively final.
The rule in action¶
import java.util.function.Consumer;
public class Player {
private String quality = "HD";
public void start(String title) {
String device = "TV";
Consumer<String> show = u ->
System.out.println(u + ": " + title + " on " + device + " in " + quality); // ❌ device
quality = "4K"; // ✅ a field
device = "Phone"; // makes device not effectively final
show.accept("Ana");
}
}
qualityis a field: changing it is fine. The lambda reads the current value when it runs.titleis never reassigned, so it's effectively final.deviceis reassigned, so it loses its status. The compiler reports the error on the lambda line, not on the reassignment:
Remove device = "Phone"; and it compiles and prints Ana: Dune on TV in 4K for start("Dune"). Note the 4K: the field changed before the lambda ran.
Changing a captured local inside the lambda is illegal too: u -> device = "Phone" doesn't compile. A lambda can read a captured local, never modify it.
Why the rule exists¶
A lambda can run later, long after the line that created it (deferred execution). Java copies the value of the local variable into the lambda, so that value must not change behind its back. Fields don't have this problem: the lambda reads them through the object each time.
Time to apply all three places in a refactoring.