Java - 从 lambda 中更改最终变量的值

Java - changing the value of a final variable from within a lambda

在Java中我有以下代码

List<Integer> myList = new ArrayList<>();
for (int i=0;i<9;i++) {
    myList.add(i);
}

Integer sum = 0;

myList.forEach(i -> {
    sum = sum + i; // does not compile, sum needs to be final or effectively final
});   

for(int i : myList) {
    sum = sum + i; //runs without problems
} 

我的问题是,为什么我不能从 lambda 中更改 sum 的值?它的作用与下面的 for 循环完全相同,还是我错了?有趣的是,如果我在 main 方法之外将整数和声明为静态,它也能正常工作。谁能解释我为什么?

编辑:在另一个类似的问题 Does Java 8 support closures 中,答案似乎是:

it is a combination of backwards compatibility and project resourcing constraints.

但是我仍然不明白为什么如果我对一个数组求和或者如果我在 main 之外声明它会起作用。我还想了解 myList.forEach 和下面的 for 循环之间的区别,为什么一个有效而另一个无效。

试试:

final Integer[] sum = new Integer[1];
sum[0] = 0;

myList.forEach(i -> {
    sum[0] = sum[0] + i; // does not compile, sum needs to be final or effectively final
}); 

因为 lambda 实际上是一种语法糖,用于初始化匿名 class(并覆盖方法)。

这和你写的一样:

final Integer[] sum = new Integer[1];
sum[0] = 0;

myList.forEach(new Consumer() {
    public void accept(Integer element) {
        sum[0] = sum[0] + element;
    }
});

来自外部范围的变量和您在内部范围内使用的变量必须是最终的(在此示例中 sum)。那只是因为 Java 不支持闭包。因此,外部变量必须标记为final。由于 Integer 本身是不可变的(如果将其声明为 final,则无法再更改它),因此您必须使用包装器对象或数组(就像我所做的那样)。

您可以在这里找到更多有用的信息:

  • Why are only final variables accessible in anonymous class?
  • Cannot refer to a non-final variable inside an inner class defined in a different method

不完全是您正在寻找的答案,但在大多数情况下,您不需要在 lambda 中修改它。这是因为 lambda 以适当的函数式风格改变状态并不是惯用的。

要获得结果,您可以使用提供的任何高级函数来屏蔽 "accumulator",然后赋值:

sum = myList.stream().mapToInt(x->x).sum();

lambda 基本上是匿名的 class。您只能从匿名 class.

访问最终局部变量

您需要的是一个可以修改其内容的包装器 class。为了快速破解,您可以在这种情况下使用 AtomicInteger

AtomicLong sum = new AtomicLong(0);
myList.forEach(i -> {
  sum.addAndGet(i); // does not matter getAndAdd or addAndGet
});

sum.get(); // to get the value

另一种方法是,如果您使用的是 Intellij IDEA,IDE 可以建议您将变量转换为最终的一个元素数组(如 darijan 的回答)。