Java 8中对可变集合的Lambda闭包

huangapple go评论165阅读模式
英文:

Java 8 Lambda closure over mutating collections

问题

以下是翻译好的内容:

我想要提交一个 lambda 表达式到 ExecutorService 中。这个 lambda 表达式捕获了一个累积的 ArrayList,但我使用 "toArray" 方法传递了一个防御性拷贝,然后清空了累积器。似乎这个 lambda 表达式没有捕获 "toArray" 方法的返回值。

但是,如果我将这个拷贝分配给 lambda 外部的另一个引用,那么它会被正确处理。

原始代码:

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())); // 有问题的部分
        accumulator.clear();
      }
    }

    executorService.shutdown();
    try {
      if (!executorService.awaitTermination(10, TimeUnit.MINUTES)) {
        System.err.println("等待了 10 分钟,强制退出");
        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]);
    }
  }
}

上述代码无法打印出完整范围的 "i" 值,我猜测在 lambda 表达式的右侧被评估以调用 accumulator.toArray() 方法的时候,accumulator 已经被修改了。

现在,如果我将这个拷贝的引用传递给 lambda,它就会正常工作。

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

编辑:

我感兴趣的是对这种行为的解释。我的错误是否因为 lambda 右侧的函数调用(toArray)只有在完整执行 lambda 表达式时才会被评估?

英文:

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&lt;Integer&gt; accumulator = new ArrayList&lt;&gt;();
    ExecutorService executorService = Executors.newFixedThreadPool(3);

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

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

  static void run(Object[] arr) {
    for (int i = 0; i &lt; 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(() -&gt; 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 ?

答案1

得分: 5

因为它是一个 lambda 函数,对 accumulator.toArray() 的调用会被延迟,直到 lambda 实际执行时。到那时,列表可能已经被清空。

英文:

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.

答案2

得分: 1

一个在初始化后其值从未更改的非最终局部变量被称为“实际上最终”变量。这个概念的引入是因为在 Java 8 之前,我们不能在匿名类中使用非最终局部变量。如果你想在匿名类中访问局部变量,就必须将其声明为 final。

当引入 Lambda 表达式时,这个限制得到了放宽。因此,如果局部变量在初始化后不会更改,那么在 Lambda 内部,就不再需要将其声明为 final。实际上,Lambda 本质上就是一个匿名类。Java 8 意识到了每次开发者使用 Lambda 都需要声明局部变量为 final 的不便,于是引入了这个概念,使得将局部变量声明为 final 不再必要。因此,如果你看到匿名类的规则仍然没有改变,只是在使用 Lambda 时不再需要每次写 final 关键字。

换句话说,你不能直接传递 accumulator 变量,因为你试图在它被最终确定后对其进行更改。

英文:

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.

huangapple
  • 本文由 发表于 2020年8月19日 02:35:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/63474695.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定