sas1138
sas1138

Reputation: 358

Java 8 Lambda closure over mutating collections

I want to submit a lambda, to an ExecutorService. The lambda is closing over an accumulating Arraylist, but I passed a defensive copy using "toArray" and then cleared the accumulator. It seems the lambda, is not capturing the response of "toArray".

But if I assign the copy to another reference outside the lambda, then its properly processed.

Original Code:

public class Test {
  public static void main(String[] args) {
    final int BATCH = 10;
    ArrayList<Integer> accumulator = new ArrayList<>();
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    for (int i = 0; i < BATCH * 10; i++) {
      accumulator.add(i);
      if (accumulator.size() >= BATCH) {
        executorService.submit(() -> run(accumulator.toArray())); // faulty
        accumulator.clear();
      }
    }

    executorService.shutdown();
    try {
      if (!executorService.awaitTermination(10, TimeUnit.MINUTES)) {
        System.err.println("Waited for 10 minutes, forced exit");
        System.exit(0);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  static void run(Object[] arr) {
    for (int i = 0; i < arr.length; i++) {
      System.out.println(arr[i]);
    }
  }
}

Above doesn't print the full range of "i", I am guessing by the time the lambda's RHS is evaluated to call the accumulator.toArray(), the accumulator is changed.

Now if I pass a reference to the copy to the lambda it works.

    final Object[] temp = accumulator.toArray();
    executorService.submit(() -> run(temp));
    accumulator.clear();

Edit:

What I am interested in is explanation of this behaviour ? Is my error because the function call (toArray) on the right hand side of a lambda is only evaluated when the full lambda is executed ?

Upvotes: 3

Views: 179

Answers (2)

djangofan
djangofan

Reputation: 29669

A non-final local variable whose value never changes after initialization is called “Effectively Final”. This concept was introduced because prior to Java 8 we could not use a non-final local variable in an anonymous class. If you have access to a local variable in Anonymous class, you have to make it final.

When Lambdas was introduced, this restriction was eased. Hence to the need to make local variable final if it’s not changed once it is initialized as Lambda in itself is nothing but an anonymous class. Java 8 realized the pain of declaring local variable final every time developer used Lambda and introduced this concept and made it unnecessary to make local variables final. So if you see the rule for anonymous class still not changed, it’s just you don’t have to write final keyword every time when using lambdas.

In other words, you cannot pass the accumulator variable directly because you are trying to mutate it after it was finalized.

Upvotes: 1

M A
M A

Reputation: 72854

Because it's a lambda, the call to accumulator.toArray() is deferred until the lambda is actually executed. By that time, the list could have been cleared.

Upvotes: 5

Related Questions