使用 Lambda 表达式时为什么不能直接传递变量?

本文最后更新于 2024年11月4日 晚上

使用 Lambda 表达式时为什么不能直接传递变量?

Lambda 表达式简介

Lambda 表达式是从 java8 开始引入的,主要用于简化匿名内部类的使用,使得代码更加简洁和易读‌.

这是不用 Lambda 表达式:

1
2
3
4
5
6
7
ExecutorService executor =  Executors.newFixedThreadPool(10);
executer.submit(new Runnable() {
@Override
public void run() {
func();
}
});

这是用 Lambda 表达式:

1
2
ExecutorService executor =  Executors.newFixedThreadPool(10);
executer.submit(() -> func());

怎么样,是不是看起来简洁了很多🤗

为什么不能直接传递变量

普通局部变量

我们先来看使用变量的代码:

1
2
3
4
ExecutorService executor =  Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> func(i));
}

这个可能看着没啥问题,但是你拉到 IDE 上二话不说就是编译错误,红波浪线直接安排上了🙄。IDE 会提示你要使用 final 类型的局部变量

IDE的提示

那么为什么在使用 Lambda 表达式的时候不能使用变量呢?

我们先看看代码中的情况,需要我们将变量置为 final。那么我们在循环内部新建一个局部变量拿到 i 的值即可:

1
2
3
4
5
ExecutorService executor =  Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int n = i;
executor.submit(() -> func(n));
}

这里的 final 加或不加都没什么影响,这样一来报错就没有了,过啦!!!

那么我们来看看原因,其实这里是因为 Lambda 表达式(匿名类) 会在另一个线程中执行。如果在线程中要直接访问一个局部变量,由于这里的局部变量是在栈中分配的,可能线程执行时该局部变量已经被销毁了,而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝。

堆中的变量

说实话到这里其实就可以结束了,但是可能有小伙伴也又有想法:如果是因为这个的原因,那么我直接在堆里搞个变量,岂不是也可以?

哇靠,难道你真的是添柴(bushi)🤩?这个理论在如果没有异步执行的情况下在 lambda 表达式中使用静态变量,是完全可行的,就像这样:

1
2
3
4
5
int[] arr = {0};
for (int i = 0; i < 10; i++) {
Arrays.stream(arr).forEach((n) -> System.out.println(arr[0]));
arr[0]++;
}

当然啦,换成 static 的或者是 AtomicInteger 类型的也是 OK 的。

PS:我这个就是随便写的,不要太纠结有什么用哈😁

然后再回到之前的代码,如果我们使用局部变量会怎么样呢?

1
2
3
4
5
ExecutorService executor =  Executors.newFixedThreadPool(10);
static int i = 0;
for (; i < 10; i++) {
executor.submit(() -> func(i));
}

震惊!!!结果是所有的线程全执行了 executor.submit(() -> func(10));

看到结果大家应该就明白是为什么了。Lambda 捕获的是 i 的引用,而不是 i 的当前值。当 for 循环结束时,i 的值已经是 10。因此所有提交的任务在执行时访问的都是 i 的最终值。导致出现了这样的问题。

结论

最后我们得出的结论是:在非异步的情况下,Lambda 表达式是可以使用存储在堆中的变量。而在异步条件下,不可以直接在 Lambda 表达式中使用循环外的局部变量。如果要在循环里使用 Lambda 且要使用外部的变量,需要在循环内部用一个最好是 final 类型的变量接住值,再传入 Lambda 中。归根结底是因为 Lambda 捕获的是引用,而不是值。


使用 Lambda 表达式时为什么不能直接传递变量?
http://cloudyw.cn/2024/11/04/使用-Lambda-表达式时为什么不能直接传递变量?/
作者
cloudyW
发布于
2024年11月4日
许可协议