在 Foreach Lambda 中使用上一个元素的 Java 流

2022-09-02 10:32:54

我有一个数字列表,里面有一些。由于在我的情况下意味着无效度量,因此我需要使用在前面的位置中找到的第一个非0元素更改值元素。0s00

例如,列表

45 55 0 0 46 0 39 0 0 0

必须成为

45 55 55 55 46 46 39 39 39 39

这是使用经典的实现for each

      int lastNonZeroVal = 0;
        for (MntrRoastDVO vo : res) {
            if (vo.getValColor() > 0) {
                lastNonZeroVal = vo.getValColor();
            } else {
                vo.setValColor(lastNonZeroVal);
            }
        }

有没有办法用Java Streams和Lambda Functions来实现这一点?

由于我知道我不能在foreach lambda中更改流的源,因此实际上列表是对象列表,我不会更改列表的元素,而只是分配新值。

这是我的第一个解决方案

int lastNonZeroVal = 1;
resutl.stream().forEach(vo -> {
        if(vo.getValColor()>0){
            lastNonZeroVal=vo.getValColor();
        }else{
            vo.setValColor(lastNonZeroVal);
        }
});

但我也在这里阅读

最好是传递给流操作的 lambda 完全没有副作用。也就是说,它们在执行期间不会改变任何基于堆的状态或执行任何 I/O。

这让我担心

数据已分区,不能保证在处理给定元素时,该元素之前的所有元素都已处理完毕。

这个解决方案会产生无效的结果,也许当列表中的元素数量很高时??事件如果我不使用 ?parallelStream()


答案 1

最好是传递给流操作的 lambda 完全没有副作用。也就是说,它们在执行期间不会改变任何基于堆的状态或执行任何 I/O。

您的解决方案确实有副作用,它会将您的源列表更改为资源列表。为避免这种情况,您需要 map 运算符并将流转换为集合。由于无法访问上一个元素,因此状态必须存储在外部的最终字段中。为了简洁起见,我使用了 Integer 而不是你的对象:

List<Integer> sourceList = Arrays.asList(45, 55, 0, 0, 46, 0, 39, 0, 0, 0);

final Integer[] lastNonZero = new Integer[1]; // stream has no state, so we need a final field to store it
List<Integer> resultList = sourceList.stream()
             .peek(integer -> {
                 if (integer != 0) {
                     lastNonZero[0] = integer;
                 }
             })
             .map(integer -> lastNonZero[0])
             .collect(Collectors.toList());

System.out.println(sourceList); // still the same
System.out.println(resultList); // prints [45, 55, 55, 55, 46, 46, 39, 39, 39, 39]

使用流来解决您的问题不是最佳解决方案,除非您需要一些其他操作,如过滤器,其他地图操作或排序。


答案 2

有一种方法可以只用流函数来做到这一点,尽管在我看来它不是特别干净。您可以创建自己的收集器,如果当前条目为零,则默认为列表中的最后一个条目。像这样:

void AddOrLast(List<Integer> list, Integer value) {
    Integer toAdd = 0;
    if (value != 0) {
        toAdd = value;
    } else {
        if (!list.isEmpty()) {
            toAdd = list.get(list.size() - 1);
        }
    }
    list.add(toAdd);
}

@Test
public void name() {
    List<Integer> nums = Arrays.asList(45, 55, 0, 0, 46, 0, 39, 0, 0, 0);

    Collector<Integer, ArrayList<Integer>, List<Integer>> nonZeroRepeatCollector =
            Collector.of(
                    ArrayList::new,
                    this::AddOrLast,
                    (list1, list2) -> { list1.addAll(list2); return list1; },
                    (x) -> x);

    List<Integer> collect = nums.stream().collect(nonZeroRepeatCollector);
    System.out.println(collect);
    // OUT: [45, 55, 55, 55, 46, 46, 39, 39, 39, 39]
}

如果非零,该方法将添加当前值,否则就是我们正在构建的数组中的最后一个条目。AddOrLast

使用供应商,蓄能器,合路器,整理器图案。nonZeroRepeatCollector

  • 供应商初始化了返回的对象。(我们的阵列列表)
  • 累加器使用新值更新返回的对象。(列表.添加)
  • 合并器用于流被拆分并需要重新加入的情况,例如在并行流中。(在我们的例子中,这不会被调用)
  • 已完成是完成集合的最终操作。在我们的例子中,只需返回 ArrayList。

推荐