【关于forEach的一些问题】

发布时间:2023年12月20日

背景

今天使用流对一个需求列表进行分组,分组后得到一个map集合
我需要循环着一个map集合,同时需要在循环外初始化一个变量,在循环内累加;
代码如下:

                Integer count = 0;
                //安全库存计算模式
                demandGroup.forEach((key,demands) ->{
                    if (count == 0){
                        count = count + 1;
                        return;
                    }
                    //生成供应
                    createSupply(planContainer,plan,demands);
                });

但是着端代码是存在问题的,直接就编译报错了;
因为Lambda表达式中引用的局部变量必须是final或者effectively final的,
这里count在Lambda表达式中被赋值,这使得它不再是final或effectively final的,因此编译器会报错;

解决方案

如果我把integer换成BigDecimal,在循环内调用add方法进行累加,这个累加会成功吗?

BigDecimal supplyCount = BigDecimal.ZERO;
//安全库存计算模式
demandGroup.forEach((key,demands) ->{
    if (BigDecimal.ZERO.compareTo(supplyCount) == 0){
        supplyCount.add(new BigDecimal(1));
        return;
    }
    //生成供应
    createSupply(planContainer,plan,demands);
});

这样编译不再报错,但是运行结果,supplyCount始终 为0,createSupply(planContainer,plan,demands);这一句代码始终没有执行;
那是因为,对象名称是相同的,但是在Lambda表达式中supplyCount已经是另外一个对象了,所以我每次add的时候都是给新的对象 +1,循环外的supplyCount一直都是0,最终导致createSupply(planContainer,plan,demands);这一句代码始终都没有执行;

那应该怎么解决?

可以使用一个一元素数组或者一个包装类(如AtomicInteger或AtomicReference)
代码如下:

AtomicReference<BigDecimal> supplyCount = new AtomicReference<>(BigDecimal.ZERO);
//安全库存计算模式
demandGroup.forEach((key,demands) ->{
    if (BigDecimal.ZERO.compareTo(supplyCount.get()) == 0){
        supplyCount.set(supplyCount.get().add(BigDecimal.ONE));
        return;
    }
    //生成供应
    createSupply(planContainer,plan,demands);
});

为什么这里使用了包装类AtomicReference就可以实现累加呢?

AtomicReference是Java提供的一个线程安全的引用类型变量,它提供了一些方法来进行原子操作。虽然AtomicReference本身并没有提供累加操作,但是我们可以通过get和set方法来实现累加操作。

在Java中,Lambda表达式中引用的局部变量必须是final或者effectively final的。这意味着这些变量在初始化后就不能再被修改。然而,这个规则只适用于变量本身,而不适用于变量引用的对象。如果变量引用的是一个对象,那么这个对象的状态是可以在Lambda表达式中被改变的。

如果你有一个数组或者一个包装类(如AtomicIntegerAtomicReference),你可以在Lambda表达式中修改这个数组或包装类的状态,即使这个数组或包装类的引用是final的。这是因为你实际上并没有改变数组或包装类的引用,而只是改变了它们的状态。

以下是一个示例,展示了如何在Lambda表达式中修改一个数组的元素:

final int[] count = new int[1];
Arrays.asList(1, 2, 3, 4, 5).forEach(i -> count[0] += i);
System.out.println(count[0]);  // 输出15

在这个示例中,我们首先创建了一个包含一个元素的数组count,然后在Lambda表达式中修改了这个数组的第一个元素。尽管count的引用是final的,但我们仍然可以修改它的状态。

虽然平时很喜欢使用流处理业务,但是出现这种bug,归根结底还是我对Lambda不够熟悉,后续深入研究一下;

文章来源:https://blog.csdn.net/zhijingege/article/details/135077584
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。