Talk is cheap, show me your code!
看一段代码:
/**
* 在lambda表达式内部,修改lambda表达式外部的局部变量的值
*/
private synchronized void updateLocalVariable() {
List<String> list = new ArrayList<>();
list.add("444");
list.add("4444");
int count = 0;
// 这样写会报错。Local variable result defined in an enclosing scope must be final or effectively final
list.stream().forEach(str -> System.out.println("当前是第" + (count++) + "次循环" + str));
}
如果你试图在lambda表达式内部修改外部局部变量的值,请注意,这里的2个限定条件:
1,count在lambda外部。
2,count是局部变量。
那么jvm会无情的告诉你:
Local variable result defined in an enclosing scope must be final or effectively final
原因其实也很简单,本质上就是因为lambda表达式在方法内部,那么lambda表达式的内存分配就是在栈上。栈内存不存在线程安全问题,因为栈内存存的都是变量的副本。
对于局部变量count而言,它的生命周期就是所在方法的生命周期。这就决定了count无法被位于同一个栈帧上的lambda修改,因为这种修改毫无意义,
你无法将你的修改传递出当前栈帧。栈内存不会被共享,也就意味着你没有权利和其他栈帧通信。
如果非要在lambda内部修改lambda表达式外部的局部变量的值呢?
有两种方式:使用数组或者把局部变量定义为全局变量。
这2种方式,其实本质是一样的:内存都分配在堆上。这就决定了,使用这2种方式来修改变量的值,是可行的。
使用数组代码如下:
/**
* 使用数组
*/
private synchronized void updateLocalVariableArray() {
List<String> list = new ArrayList<>();
list.add("444");
list.add("4444");
int[] count = {0};
list.stream().forEach(str -> System.out.println(count[0]++));
}
使用全局变量也可以,很简单,这里不再列出代码。但是有个问题,全局变量需要考虑线程安全问题。
接下来我们讨论另外一个问题:如何用lambda替换for循环,实现计数功能。
我要知道当前处理的是第几行,比如每100条记录提交一次什么的,类似这种功能,平时需求还是很常见的。
代码实例如下:
private void updateLocalVariableForLoop() {
List<String> list = new ArrayList<>();
list.add("444");
list.add("4444");
list.add("090");
// 传统for循环方式
int count = 0;
for (String str : list) {
System.out.println("当前是第" + (count++) + "次循环" + str);
}
}
这里提供3种方式:
1,内部类方式 参考方法 updateLocalVariableInnerClass
2,lambda方式 参考方法 updateLocalVariableLambda
3,外部类方式 参考方法 updateLocalVariableOuterClass
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* 如何用lambda替换for循环,实现计数功能。
*/
public class LambdaLocalVariableUpdate {
/**
* 全局变量
*/
private static int GLOBAL_COUNT = 0;
public static void main(String[] args) {
LambdaLocalVariableUpdate lambdaLocalVariableUpdate = new LambdaLocalVariableUpdate();
// lambdaLocalVariableUpdate.updateLocalVariableInnerClass(); // 通过对象来实现计数,绕开本地变量,但是存在 线程安全问题
// lambdaLocalVariableUpdate.updateLocalVariableLambda(); // 通过对象来实现计数,绕开本地变量,但是存在 线程安全问题
// System.out.println(Thread.currentThread().getName() + "-lambda外部变量GLOBAL_INT = " + GLOBAL_INT);
// 启动多线程,测试是否存在线程安全问题。
for (int i = 0; i < 30; i++) {
Thread thread = new Thread(() -> lambdaLocalVariableUpdate.updateLocalVariableInnerClass());
thread.setName("线程-" + (i + 1));
thread.start();
}
}
/**
* 使用lambda方式
* 不存在线程安全问题
*/
private void updateLocalVariableLambda() {
List<String> list = new ArrayList<>();
list.add("444");
list.add("4444");
list.add("090");
Stream.iterate(0, i -> i + 1).limit(list.size()).forEach(index -> {
String str = list.get(index);
System.out.println("当前是第" + index + "次循环" + str);
});
}
/**
* 内部类方式,不存在线程安全问题,但是不易扩展。
*
*/
private void updateLocalVariableInnerClass() {
CountInner count = new CountInner();
List<String> list = new ArrayList<>();
list.add("444");
list.add("4444");
list.add("090");
list.stream().forEach(str -> System.out.println("当前是第" + count.count() + "次循环" + str));
}
/**
* 外部类方式,不存在线程安全问题,但是变量要定义为非静态,如果定义为静态,则存在线程安全问题。
*
*/
private void updateLocalVariableOuterClass() {
CountOuter count = new CountOuter();
List<String> list = new ArrayList<>();
list.add("444");
list.add("444");
list.add("555");
list.stream().forEach(str -> System.out.println("当前是第" + count.count() + "次循环" + str));
}
/**
* 内部类
*/
class CountInner {
private int i = 0;
public Integer count() {
return i++;
}
}
}
class CountOuter {
private int i = 0;
public Integer count() {
return i++;
}
}
最后,要特别说明一下,如果你要修改的是某个对象的值,比如List集合,那是完全没问题的。
/**
* 在lambda内部修改List集合
*/
private void updateLocalVariableCollection() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(100);
List<Integer> list2 = new ArrayList<>(list.size());
list.stream().forEach(intVal -> {
if (intVal > 0) {
// 在lambda内部,修改list2
list2.add(intVal);
}
});
}