为什么lambda表达式中的变量必须是final或有效final的?

10
当我编写这段代码时,我遇到了一个编译时错误,错误提示为:'Variables in lambdas must be final or effectively final'
现在,我明白了从以下代码中移除i可以解决这个问题: futureLists.add(executorService.submit( () -> "Hello world" + i)); 但是我想知道为什么会有这样的要求?
根据JLS的规定,它只说:
“在Lambda表达式中使用但未声明的任何局部变量、形式参数或异常参数必须声明为final或有效final,否则将在尝试使用时出现编译时错误。”
但它没有说明为什么需要这样的要求。那么Java工程师为什么要强制实施这样的要求呢?
public class test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
         ExecutorService executorService = Executors.newFixedThreadPool(10);

         List<Future<String>> futureLists = new ArrayList<>();

         for (int i = 0; i < 20; i++) {
             futureLists.add(executorService.submit( () -> "Hello world" + i));
             }

         for (Future<String> itr:futureLists) {
             System.out.println(itr.get());
            }
       }
   }

3
这里说:“限制有效的 final 变量是为了防止访问动态更改的本地变量,因为这样会可能引入并发问题。” - Dioxin
如果您运行一个lambda async,下一行将执行,但不能保证它会被第二个线程更新。匿名对象也有同样的限制。此外,SO上有关于确切问题的相关问题。 - Marcos Vasconcelos
3
其中一些重复内容并不是真正的重复。答案不会使问题成为重复。所有这些重复问题只会让人更难找到实际的答案。事实上,大部分答案甚至都与此问题无关。难怪人们在提出可能已经在这里得到解答的问题时不太关心 - 他们被迫筛选一堆无关的答案才能得到他们问题的答案。 - Dioxin
3个回答

18

这与多线程编程有关。

Java 中的局部变量直到现在都免于竞态条件和可见性问题,因为它们只对声明它们的方法执行的线程可访问。但是,lambda 表达式可以从创建它的线程传递到不同的线程,如果由第二个线程评估的 lambda 获得了更改局部变量的能力,那么该免疫性将会丢失。- 来源


2
不仅限于多线程编程。即使在单线程中,Lambda也可能比函数存在更长的时间,此时原始变量已经不存在了。这就是为什么闭包具有副本变量而不能实际修改原始变量的原因,同时JVM不支持变量引用。将它们独立可变会很混乱,所以它们必须是final。 - Jan Hudec

4
Java工程师这样做的可能原因是为了使Java代码更加耐用。如果允许变量不是final,它的值可以从程序的任何地方进行修改。这可能会导致并发问题。在这个文档中提到了这一点:
“有效最终变量的限制禁止访问动态改变的局部变量,其捕获可能会引入并发问题。相对于final的限制,它减少了程序员的劳动负担。”

0

不可变的事物易于分发。多个使用者可以使用它们,而无需担心其他人可能已更改了值。 Java中的Lambda函数将Java带入了函数式编程领域,您可以在其中传递函数。这在分布式编程中非常有用。由于可能存在多个处理器,因此最好传递最终值,而不是添加并发管理的开销。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接