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);
}
});
}