如何在lambda內部修改外部局部變量的值/如何用lambda重構for循環計數

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章