编译:ImportNew/覃佑桦
    dzone.com/articles/modifying-variable-inside-lambda

    本文将讨论如何使用 Java AtomicInteger 和数组修改 Lambda 表达式中的变量值。

    有时会遇到需要在 Lambda 表达式中修改变量的情况。然而,当尝试修改时会报告编译错误:

    Lambda 表达式中的变量必须是 final 或 effective final 变量。

    让我们通过一个例子了解,什么时候需要修改 Lambda 表达式 中的变量值。

    Romzo 在 FedEx 工作。FedEx 采用可变大小预付容器(第一件物品大小 + 6),具体结果取决于加入容器的第一件物品重量。假设加入的第一件物品重量是5,那么容器可以容纳单件物品重量至多为5 + 6,即11。

    给定物品重量列表为1,1,2,3,21,7,12,14,21,计算最少需要多少个集装箱。

    解答:

    货品重量分别为:{1, 2, 3, 21, 7, 12, 14, 21}。列表可分为3个容器:{1,2,3,7}、{12,14} 和 {21,21}。

    每个容器中的货品重量 < 最小货品重量 + 6 单位重量。

    说明

    • 第一个容器可容纳重量分别为1,2、3和7的物品(最大重量为7,即1+ 6)
    • 第二个容器容纳物品重量为12,14。(最大重量18,即12 + 6 = 18)
    • 第三个容器存放物品重量为21,21。(最大重量为27,即21 + 6 = 27)

    不使用 Lambda 表达式:

    1. public static void main(String[] args) {
    2. int[] items = {1, 2, 3, 21, 7, 12, 14, 21};
    3. int count = 1;
    4. Arrays.sort(items); // 按照重量升序排列
    5. int currentWeight = items[0];
    6. for (int weight : items) {
    7. if (!(weight <= currentWeight + 6)) {
    8. count++;
    9. currentWeight = weight;
    10. }
    11. }
    12. System.out.println(count);
    13. }

    在尝试用 lambda 表达式和 stream 方案时,发现更新 lambda 表达式中的 count 变量有问题。

    无法更新声明为 final 的字段:

    1. public static void main(String[] args) {
    2. final int count = 1; // Lambda 內部变量必须标记为 final 或实际作为 final 使用
    3. final int t = 0; // Lambda 內部变量必须标记为 final 或实际作为 final 使用
    4. int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
    5. int k = 6;
    6. Arrays.sort(A);
    7. IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
    8. if (!(A[t] + k >= A[x])) {
    9. count++; // 由于 count 是 final 变量, 不能修改
    10. t = x; // 由于 tcount 是 final 变量, 不能修改
    11. }
    12. return count;
    13. }).collect(Collectors.toList());
    14. System.out.println(count);
    15. }

    Java 8语言规范§15.27.2提出了上述问题:

    使用任何 lambda 表达式外部声明的局部变量、形式参数或异常参数
    必须声明为 final或 effective final 变量(§4.12.4),
    否则会报告编译错误。

    要克服这个问题有3种方案:

    1. 使静态变量
    2. 使用数组
    3. AtomicInteger

    1.如果把变量 count 和 t 变为 static,在多线程环境中会发生读写冲突问题。

    假如可以确认除当前正在使用线程外没有其它线程,这么干没有问题。最好避免使用静态变量。

    1. static int count = 1;
    2. static int t = 0;
    3. public static void main(String[] args) {
    4. int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
    5. int k = 6;
    6. Arrays.sort(A);
    7. IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
    8. if (!(A[t] + k >= A[x])) {
    9. count++;
    10. t = x;
    11. }
    12. return count;
    13. }).collect(Collectors.toList());
    14. System.out.println(count);
    15. }

    2.用数组取代静态变量。这种方案充分利用了数组的优势。请记住,只有 lamdba 表达式外面的变量必须为 final;这个方法其实绕过了 lambda 的限制。

    1. public static void main(String[] args) {
    2. final int[] count = {0}; // final
    3. final int[] t = {0}; // final
    4. int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
    5. int k = 6;
    6. Arrays.sort(A);
    7. IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
    8. if (!(A[t[0]] + k >= A[x])) {
    9. count[0] = count[0] + 1; // 保证外部变量为 final 的前提下可以工作
    10. t[0] = x; // 保证外部变量为 final 的前提下可以工作
    11. }
    12. return count;
    13. }).collect(Collectors.toList());
    14. System.out.println(count[0] + 1);
    15. }

    3.使用 AtomicInteger。这样可以确保 int 值会以原子操作执行,从而确保代码线程安全。它提供的 方法包括getAndIncrement()、set()、addAndGet()等,可以按原子操作执行。

    1. public static void main(String[] args) {
    2. int[] A = {1, 2, 3, 21, 7, 12, 14, 21};
    3. int k = 6;
    4. AtomicInteger count = new AtomicInteger(0); // 实际上为 final
    5. final AtomicInteger t = new AtomicInteger(0); // final
    6. Arrays.sort(A);
    7. IntStream.rangeClosed(0, A.length - 1).boxed().map(x -> {
    8. if (!(A[t.intValue()] + k >= A[x])) {
    9. count.incrementAndGet();
    10. t.set(x);
    11. }
    12. return count;
    13. }).collect(Collectors.toList());
    14. System.out.println(count.incrementAndGet());
    15. }

    final 与 Effective final 的区别:
    final 或 effectively final 变量一旦初始化就不能更改,因此不能再循环中或者内部类里初始化 。

    final 变量:
    final int x;
    x = 3;
    Effectively final 变量:

    1. int x;
    2. x = 4; // 永远不修改变量值, 实际等价于 final
    3. 变量前使用关键字为 final 变量, 不加关键字但永远不修改变量值为实际上的 final 变量。