Java 8 Lambda变量范围

我有2个代码示例:

int[] idx = { 0 }; List list = new ArrayList(); list.add("abc"); list.add("def"); list.add("ghi"); list.stream().forEach(item -> { System.out.println(idx[0] + ": " + item); idx[0]++; }); 

好好工作。

虽然此代码有编译错误:

 int idx = 0; List list = new ArrayList(); list.add("abc"); list.add("def"); list.add("ghi"); list.stream().forEach(item -> { System.out.println(idx + ": " + item); idx++; }); 

他说:

 Local variable idx defined in an enclosing scope must be final or effectively final. 

唯一的区别是idx int或int数组。

根本原因是什么?

根本原因是JVM缺少构造对局部变量的引用的机制,这是当idxint或某些不可变类型(例如String )时执行idx++所需要的。 既然你试图改变idx ,那么仅仅捕获它的值是不够的; Java需要捕获引用,然后通过它修改值。

使用数组时Java没有此问题,因为数组是引用对象。 Java可以捕获永不改变的数组引用,并使用该不变的引用来改变对象。 数组本身提供了必要的间接级别,因为Java数组是可变的。

我尝试使idx静态并将其移出main方法,工作正常。 但为什么?

因为在这种情况下,lambda不需要捕获对基本类型的局部变量的引用。 对静态变量的引用很容易获得,因此捕获它没有问题。

类似地,如果将idx作为成员变量,并在实例方法中使用lambda,代码将起作用。 这将让lambda通过this对象修改idx字段,可以自由捕获。

我对你的观察有部分解释。 Java 8代码中的初始化数组被认为是最终的 ,因为它的值在初始化后不会改变。 这就是int[] idx = { 0 }; 您的代码版本正在通过。 所以我希望如果你有效地使int idx最终,那么它也会通过。 一种方法是通过声明它来正式使这个变量最终,即final int idx = 0

在初始化之后其值永远不会改变的变量或参数实际上是最终的。

在案例1中:

int[] idx不会改变,如果你替换idx[0]++;idx = {1}; 将编译错误

在案例2中:

如果你删除idx++ ; 它会编译好