148. Java Lambda 表达式 - 捕获局部变量
一旦你习惯了 Lambda
表达式的使用,它们会变得非常自然。Lambda
与 Collections Framework
、Stream API
以及 JDK
中的许多其他部分无缝集成。从 Java SE 8
开始,Lambda
表达式几乎无处不在,它们成为了 Java
编程中的一个重要特性。
但是,使用 Lambda
表达式时有一些限制,可能会导致你遇到编译时错误,了解这些限制非常重要。
让我们看一下以下的代码示例:
java
int calculateTotalPrice(List<Product> products) {
int totalPrice = 0; // 局部变量
Consumer<Product> consumer = product -> totalPrice += product.getPrice(); // Lambda 表达式
for (Product product : products) {
consumer.accept(product); // 调用 consumer 的 accept 方法
}
return totalPrice;
}
看起来这段代码是没有问题的,但实际上,编译器会给出如下错误:
"lambda
表达式中使用的变量应为 final
或实际上是 final
"
原因:
Lambda
表达式无法修改它所捕获的外部变量。Lambda
表达式可以读取外部变量的值,但前提是这些变量必须是 final
的或者实际上是 final
的。捕获外部变量意味着 Lambda
只能访问并使用它们的值,而不能修改它们。
何为"final
"变量?
在 Java
中,final
变量是不可变的,它的值一旦被赋值就不能再更改。在 Lambda
表达式中,final
变量实际上是常量,可以直接读取。但 Lambda
表达式不能修改它们,因为它们在 Lambda
内部的作用域中是只读的。
为什么必须是 final
或 实际上是 final
?
Java
中的 Lambda
表达式不能修改它们所捕获的外部变量,这是为了保证并发安全性和避免不必要的副作用。如果允许修改外部变量,会引发线程安全问题或难以预料的行为。
Java SE 8
引入了"实际上是 final
"的概念。即使你没有显式声明变量为 final
,编译器也会为你推断出它是否是 final
。如果一个变量在 Lambda
表达式中被引用,但没有被修改,那么编译器会自动将其视为 final
。因此,你不一定要显式地加上 final
关键字,编译器会根据使用情况自动处理。
例子:修复代码中的问题
如果我们修改代码中的 totalPrice
变量,使它在 Lambda
表达式中变为"实际上是 final
",编译器就不会报错了。为了避免错误,可以使用 AtomicInteger
来处理这种情况,因为 AtomicInteger
是可变的,可以在 Lambda
中进行修改。修改后的代码如下:
java
import java.util.concurrent.atomic.AtomicInteger;
int calculateTotalPrice(List<Product> products) {
AtomicInteger totalPrice = new AtomicInteger(0); // 使用 AtomicInteger
Consumer<Product> consumer = product -> totalPrice.addAndGet(product.getPrice()); // 通过 addAndGet 修改值
for (Product product : products) {
consumer.accept(product);
}
return totalPrice.get(); // 返回最终的价格
}
这里使用 AtomicInteger
来代替原始的 int
类型,因为 AtomicInteger
提供了线程安全的可变值,可以安全地在 Lambda
表达式中进行修改。
总结:
Lambda
表达式只能捕获final
或"实际上是final
"的局部变量。 也就是说,Lambda
只能读取外部变量的值,不能修改它们。- "实际上是
final
" 是Java SE 8
中引入的新概念,意味着编译器可以自动推断哪些变量是不可修改的,从而避免你显式声明final
。 - 如果需要修改外部变量的值,可以使用线程安全的类,如
AtomicInteger
来替代普通变量。
通过理解这些限制和解决方案,可以更好地使用 Lambda
表达式,并避免常见的编译错误。