Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Lambda closure over mutating collections

Tags:

java

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 ?

like image 740
sas1138 Avatar asked Jun 18 '26 16:06

sas1138


2 Answers

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.

like image 77
M A Avatar answered Jun 21 '26 05:06

M A


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.

like image 24
djangofan Avatar answered Jun 21 '26 05:06

djangofan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!