如何在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);
        }
    });
}

 

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