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 and this, 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;
  }
}
 OUTPUT on compiling the above code
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
Both lines are rejected with the same message. Note that the second error is reported at the lambda, although the problem is the assignment 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 the final 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.
Java 8 lambda variable capture - a copy of an effectively final local variable and a reference to this for fields

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.
this inside a Java 8 lambda refers to the enclosing object, while this inside an anonymous class refers to the anonymous object
Because a lambda shares the enclosing scope, it also cannot declare a parameter or local variable with the same name as a local variable of the method - 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 and this.
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());
  }
}
 OUTPUT of the above code
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: bonusRate is assigned once and never changed, so the lambda can use it without it being declared final.
  • Part 2: in a for-each loop, name is a new variable on every pass, so it is effectively final and each lambda captures its own name. The index i of a classic for loop is changed by i++, so it cannot be captured; copying it into position gives 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: raisesGiven is a field, so raisesGiven++ inside the lambda is really this.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; AtomicInteger is. Usually the cleanest answer is not to count in a lambda at all - here names.size(), or count() on a stream.
  • Part 5: in the lambda, this is the LambdaCapture object, so this.company works. In the anonymous class, this is an object of the compiler-generated class LambdaCapture$1, and the field must be reached as LambdaCapture.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() or count() 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.
  • this is 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 this keeps 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 captured this. 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.