Java 8 Lambdas: Effectively Final, Capture and this
Introduction
This tutorial explains how Java 8 lambdas use variables from the code around them: what effectively final means, why lambdas can only use local variables that are effectively final, how variable capture works for local variables and for fields, and what this means inside a lambda compared with an anonymous class. We will start with the compiler error that every Java developer meets sooner or later, and the rule behind it. We will then see what a lambda actually captures, run an example covering loops, fields, counters andthis, and finish with the practical rules to remember.(This tutorial assumes you know how to write lambda expressionsClick to read: lambda expressions.)
The error: "must be final or effectively final"
A lambda may read local variables of the method it is written in, but not every local variable. The code below tries two things that are not allowed: changing a local variable inside a lambda, and using a local variable that is reassigned elsewhere.Java 8 code that breaks the effectively final rule
//CaptureErrors.java
import java.util.function.Supplier;
public class CaptureErrors {
public static void main(String[] args) {
int count = 0;
Runnable increment = () -> count++; // modifies a captured local
String name = "Tom Jones";
name = "Thomas Jones"; // reassigned, so not effectively final
Supplier<String> greet = () -> "Hello " + name;
}
}
CaptureErrors.java:7: error: local variables referenced from a lambda expression must be final or effectively final
Runnable increment = () -> count++; // modifies a captured local
^
CaptureErrors.java:11: error: local variables referenced from a lambda expression must be final or effectively final
Supplier<String> greet = () -> "Hello " + name;
^
2 errors
name = "Thomas Jones" two lines earlier: assigning name a second time is what stops it from being effectively final.
What is effectively final
A local variable or parameter is effectively final if it is never assigned again after it is initialised - so adding thefinal keyword to it would not cause a compile error. Java 8 introduced the term: Java 7 required anonymous classes to use only variables explicitly declared final, while Java 8 lets lambdas and anonymous classes use any effectively final variable without the keyword.The rule applies to local variables and parameters only. Instance fields and static fields can be read and changed freely inside a lambda.
Why the rule exists - variable capture
A lambda is often run later - by a thread pool, a stream, an event handler - when the method that created it may already have returned and its local variables are gone from the stack. So the lambda cannot refer to the variable itself. Instead, when the lambda is created, it captures a copy of the variable's value. If the variable could still change afterwards, the copy and the variable would disagree, and code would behave in surprising ways - especially across threads. Requiring the variable to be effectively final makes the copy always correct.Fields work differently. A lambda in an instance method captures the reference
this, which is itself effectively final, and reaches fields through it. The fields live on the heap with the object, so the lambda sees and changes the real field, not a copy.
this in a lambda versus an anonymous class
A lambda is not a new scope. Inside its body,this, local variable names and method names mean exactly what they mean in the surrounding method. An anonymous class, in contrast, creates a new class and a new object: inside it, this is the anonymous object, and the enclosing object has to be reached as OuterClass.this.
String name = "Tom"; Function<String, String> f = name -> name; fails with "variable name is already defined". An anonymous class may shadow such names.
Java 8 code example of variable capture
The example below runs inside an instance method, so that it can use fields andthis.
Java 8 code showing effectively final variables, capture in loops, fields, counters and this in lambdas and anonymous classes
//LambdaCapture.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
public class LambdaCapture {
private int raisesGiven = 0; // an instance field
private final String company = "JavaBrahman Ltd";
public static void main(String[] args) {
new LambdaCapture().run();
}
void run() {
// 1. An effectively final local variable: assigned once, never changed
double bonusRate = 0.10;
Function<Double, Double> bonus = salary -> salary * bonusRate;
System.out.println("1. bonus on 5200: " + bonus.apply(5200.0));
// 2. Loops: the for-each variable is new on every pass; an index needs a copy
List<String> names = Arrays.asList("Tom", "Harry", "Nancy");
List<Supplier<String>> tasks = new ArrayList<>();
for (String name : names) {
tasks.add(() -> "Hi " + name);
}
for (int i = 0; i < names.size(); i++) {
final int position = i + 1; // i itself changes, so copy it
tasks.add(() -> "#" + position);
}
StringBuilder results = new StringBuilder();
tasks.forEach(task -> results.append(task.get()).append(" "));
System.out.println("2. " + results.toString().trim());
// 3. Fields are reached through 'this', so a lambda may change them
Runnable giveRaise = () -> raisesGiven++;
giveRaise.run();
giveRaise.run();
System.out.println("3. raises given: " + raisesGiven);
// 4. Counting inside a lambda: an array or, safer, an AtomicInteger
int[] counter = {0};
AtomicInteger atomicCounter = new AtomicInteger();
names.forEach(n -> {
counter[0]++;
atomicCounter.incrementAndGet();
});
System.out.println("4. array counter " + counter[0]
+ ", AtomicInteger " + atomicCounter.get()
+ ", or just names.size() = " + names.size());
// 5. 'this' in a lambda is the enclosing object...
Supplier<String> fromLambda =
() -> this.getClass().getSimpleName() + " / " + this.company;
// ...but in an anonymous class it is the anonymous object itself
Supplier<String> fromAnonymous = new Supplier<String>() {
@Override
public String get() {
return this.getClass().getName() + " / " + LambdaCapture.this.company;
}
};
System.out.println("5. lambda this: " + fromLambda.get());
System.out.println(" anonymous this: " + fromAnonymous.get());
}
}
1. bonus on 5200: 520.0 2. Hi Tom Hi Harry Hi Nancy #1 #2 #3 3. raises given: 2 4. array counter 3, AtomicInteger 3, or just names.size() = 3 5. lambda this: LambdaCapture / JavaBrahman Ltd anonymous this: LambdaCapture$1 / JavaBrahman Ltd
Explanation of the code
- Part 1:
bonusRateis assigned once and never changed, so the lambda can use it without it being declaredfinal. - Part 2: in a for-each loop,
nameis a new variable on every pass, so it is effectively final and each lambda captures its own name. The indexiof a classic for loop is changed byi++, so it cannot be captured; copying it intopositiongives each lambda an effectively final variable of its own. The tasks run after both loops have finished, and still print the right values - because each captured a copy. - Part 3:
raisesGivenis a field, soraisesGiven++inside the lambda is reallythis.raisesGiven++and changes the field. - Part 4: a one-element array is a common trick for counting inside a lambda: the array reference is effectively final while its element changes. It is not thread-safe;
AtomicIntegeris. Usually the cleanest answer is not to count in a lambda at all - herenames.size(), orcount()on a stream. - Part 5: in the lambda,
thisis theLambdaCaptureobject, sothis.companyworks. In the anonymous class,thisis an object of the compiler-generated classLambdaCapture$1, and the field must be reached asLambdaCapture.this.company.
Rules to remember
- Locals and parameters used in a lambda must be assigned exactly once. If you need a changing value, compute it before the lambda, or copy it into a new variable.
- Do not fight the rule with arrays and holders in streams: shared mutable state is exactly what breaks parallel streamsClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples. Use
reduce(),collect()orcount()to produce results instead. - Fields are allowed but not free: a lambda that changes a field and runs on another thread needs the usual synchronizationClick to read: synchronization.
thisis the enclosing object in a lambda. If you need the function object itself, for example for recursion, use a field or an anonymous class.- Capturing
thiskeeps the object alive: a long-lived lambda that uses a field holds a reference to the whole enclosing object, which matters for listeners and caches.
Summary
In this tutorial we first saw the "must be final or effectively final" compile error, and that a variable is effectively final if it is never assigned again. Next we saw why: a lambda captures a copy of each local variable it uses, while it reaches fields through a capturedthis. We then compared this in a lambda, which is the enclosing object, with this in an anonymous class, which is the anonymous object, and saw that lambdas cannot shadow local names. We then ran an example of capture in loops, field updates, counters and both kinds of this. Lastly, we listed the rules to remember.Java 8 Tutorials
Java 8 Primitive Functional Interfaces TutorialClick to read tutorial: Java 8 Primitive Functional Interfaces Tutorial Java 8 - Handling Checked Exceptions in LambdasClick to read tutorial: Java 8 - Handling Checked Exceptions in Lambdas Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial
Java 8 Primitive Functional Interfaces TutorialClick to read tutorial: Java 8 Primitive Functional Interfaces Tutorial Java 8 - Handling Checked Exceptions in LambdasClick to read tutorial: Java 8 - Handling Checked Exceptions in Lambdas Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial