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缺少构造对局部变量的引用的机制,这是当idx
是int
或某些不可变类型(例如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++
; 它会编译好