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

Pan*_*ciz 12 java lambda

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

例如列表

45 55 0 0 46 0 39 0 0 0
Run Code Online (Sandbox Code Playgroud)

必须成为

45 55 55 55 46 46 39 39 39 39
Run Code Online (Sandbox Code Playgroud)

这是使用经典的实现 for each

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

有没有办法用Java Streams和Lambda函数实现它?

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

这是我的第一个解决方案

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

但我也在这里读到

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

这让我很担心

数据是分区的,不能保证在处理给定元素时,已经处理了该元素之前的所有元素.

这个解决方案是否会产生无效结果,可能是当列表中的元素数量很高时??如果我不使用活动parallelStream()

Jou*_*ner 9

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

您的解决方案实际上有副作用,它会将您的源列表更改为资源列表.为避免这种情况,您需要map运算符并将流转换为Collection.因为您无法访问前一个元素,所以必须将状态存储在最终字段外部.出于简洁的原因,我使用了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]
Run Code Online (Sandbox Code Playgroud)

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


bsy*_*syk 5

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

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]
}
Run Code Online (Sandbox Code Playgroud)

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

使用nonZeroRepeatCollector供应商、累加器、组合器、整理器模式。

  • 供应商初始化了返回的对象。(我们的数组列表)
  • 累加器用新值更新返回的对象。(列表.添加)
  • 组合器用于流被分割并需要重新合并的情况,例如并行流。(在我们的例子中不会被调用)
  • 完成是完成集合的最后一个动作。在我们的例子中,只需返回 ArrayList。


Ser*_*kin 1

您可以修改流内对象的状态。但您无法修改数据源状态。

该问题与经典迭代中的问题相同。

用于forEachOrdered()执行

如果流具有定义的遇到顺序,则按流的遇到顺序对该流的每个元素执行操作

如果你打电话

result.stream.forEachOrdered(...)
Run Code Online (Sandbox Code Playgroud)

所有元素将按顺序依次处理。

对于顺序流forEach似乎尊重顺序。