使用 Lambda 表达式时为什么不能直接传递变量?
本文最后更新于 2024年11月4日 晚上
使用 Lambda 表达式时为什么不能直接传递变量?
Lambda 表达式简介
Lambda 表达式是从 java8 开始引入的,主要用于简化匿名内部类的使用,使得代码更加简洁和易读.
这是不用 Lambda 表达式:
1 |
|
这是用 Lambda 表达式:
1 |
|
怎么样,是不是看起来简洁了很多🤗
为什么不能直接传递变量
普通局部变量
我们先来看使用变量的代码:
1 |
|
这个可能看着没啥问题,但是你拉到 IDE 上二话不说就是编译错误,红波浪线直接安排上了🙄。IDE 会提示你要使用 final 类型的局部变量
那么为什么在使用 Lambda 表达式的时候不能使用变量呢?
我们先看看代码中的情况,需要我们将变量置为 final。那么我们在循环内部新建一个局部变量拿到 i 的值即可:
1 |
|
这里的 final 加或不加都没什么影响,这样一来报错就没有了,过啦!!!
那么我们来看看原因,其实这里是因为 Lambda 表达式(匿名类) 会在另一个线程中执行。如果在线程中要直接访问一个局部变量,由于这里的局部变量是在栈中分配的,可能线程执行时该局部变量已经被销毁了,而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝。
堆中的变量
说实话到这里其实就可以结束了,但是可能有小伙伴也又有想法:如果是因为这个的原因,那么我直接在堆里搞个变量,岂不是也可以?
哇靠,难道你真的是添柴(bushi)🤩?这个理论在如果没有异步执行的情况下在 lambda 表达式中使用静态变量,是完全可行的,就像这样:
1 |
|
当然啦,换成 static 的或者是 AtomicInteger 类型的也是 OK 的。
PS:我这个就是随便写的,不要太纠结有什么用哈😁
然后再回到之前的代码,如果我们使用局部变量会怎么样呢?
1 |
|
震惊!!!结果是所有的线程全执行了 executor.submit(() -> func(10));
看到结果大家应该就明白是为什么了。Lambda 捕获的是 i 的引用,而不是 i 的当前值。当 for 循环结束时,i 的值已经是 10。因此所有提交的任务在执行时访问的都是 i 的最终值。导致出现了这样的问题。
结论
最后我们得出的结论是:在非异步的情况下,Lambda 表达式是可以使用存储在堆中的变量。而在异步条件下,不可以直接在 Lambda 表达式中使用循环外的局部变量。如果要在循环里使用 Lambda 且要使用外部的变量,需要在循环内部用一个最好是 final 类型的变量接住值,再传入 Lambda 中。归根结底是因为 Lambda 捕获的是引用,而不是值。