我有2个代码示例:
int[] idx = { 0 };
List<String> list = new ArrayList<String>();
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<String> list = new ArrayList<String>();
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 array。
根本原因是什么?
答案 0 :(得分:7)
根本原因是JVM缺少构造对局部变量的引用的机制,这是idx++
为idx
或某些不可变类型时执行int
所需的内容(例如{ {1}})。既然你试图改变String
,那么仅仅捕捉它的价值是不够的; Java需要捕获引用,然后通过它修改值。
使用数组时Java没有此问题,因为数组是引用对象。 Java可以捕获永不改变的数组引用,并使用该不变的引用来改变对象。数组本身提供了必要的间接级别,因为Java数组是可变的。
我尝试将
idx
设为静态并将其移出主方法,工作正常。但为什么呢?
因为在这种情况下,lambda不需要捕获对基本类型的局部变量的引用。对静态变量的引用很容易获得,因此捕获它没有问题。
同样,如果您将idx
作为成员变量,并在实例方法中使用lambda,则代码将起作用。这将允许lambda通过idx
对象修改idx
字段,可以自由捕获。
答案 1 :(得分:2)
我对你的观察有部分解释。 Java 8代码中的初始化数组被视为有效最终,因为它的值在初始化后不会更改。这就是您的代码的int[] idx = { 0 };
版本正在通过的原因。所以我希望如果你让int idx
有效地最终,那么它也会通过。实现这一目标的一种方法是正式通过声明这个变量来使这个变量最终,即final int idx = 0
。
答案 2 :(得分:1)
一个变量或参数,其值在初始化后永远不会改变,实际上是最终的。
案例1:
int[] idx
如果您将idx[0]++;
替换为idx = {1};
将会编译错误
案例2:
如果您删除idx++
;它会编译好