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 的回答)。
在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 的回答)。