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

时间:2016-09-02 09:38:19

标签: java lambda

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

例如列表

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函数实现它?

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

这是我的第一个解决方案

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

但我也读过here

  

如果传递给流操作的lambda完全是侧面的话,那是最好的   无效。也就是说,他们不会改变任何基于堆的状态或   在执行期间执行任何I / O.

这让我很担心

  

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

此解决方案是否会产生无效结果,可能是列表中的元素数量很高? ? 如果我不使用parallelStream()会发生什么事?

4 个答案:

答案 0 :(得分:8)

  

最好将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]

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

答案 1 :(得分:0)

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

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

使用forEachOrdered()执行

  

此流的每个元素的操作,如果流具有已定义的遭遇顺序,则按流的遭遇顺序

如果你打电话

result.stream.forEachOrdered(...)

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

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

答案 2 :(得分:0)

首先,你不应该在lambda中改变状态。也就是说,您可以使用扩展ArrayList的自定义列表并覆盖iterator()spliterator()方法。

请注意,这是使用 Pair 类,为简洁起见,我在此省略。

public class MemoList extends ArrayList<Pair<Integer,MntrRoastDVO>> {
    private static final long serialVersionUID = -2816896625889263498L;

    private final List<MntrRoastDVO> list;

    private MemoList(List<MntrRoastDVO> list) {
        this.list = list;
    }

    public static MemoList of(List<MntrRoastDVO> list) {
        return new MemoList(Objects.requireNonNull(list));
    }

    @Override
    public Iterator<Pair<Integer,MntrRoastDVO>> iterator() {
        Iterator<MntrRoastDVO> it = list.iterator();

        return new Iterator<Pair<Integer,MntrRoastDVO>>() {
            private Integer previous = null;

            @Override
            public boolean hasNext() {
                return it.hasNext();
            }

            @Override
            public Pair<Integer,MntrRoastDVO> next() {
                MntrRoastDVO next = it.next();
                Pair<Integer,MntrRoastDVO> pair = new Pair<>(previous, next);

                if (next.getValColor() > 0) {
                    previous = next.getValColor();
                }

                return pair;
            }

        };
    }

    @Override
    public Spliterator<Pair<Integer,MntrRoastDVO>> spliterator() {
        return Spliterators.spliterator(iterator(), list.size(), Spliterator.SIZED);
    }
}

我会像这样使用它。

public void doWork(List<MntrRoastDVO> res)
{
    MemoList.of(res).stream().forEach(this::setData);
}

private void setData(Pair<Integer,MntrRoastDVO> pair)
{
    MntrRoastDVO data = pair.two();

    if (data.getValColor() <= 0)
    {
        data.setValColor(pair.one());
    }
}

请注意,这不是使用并行流测试的。事实上,我几乎可以肯定它不会并行工作。

答案 3 :(得分:0)

有一种方法可以仅使用流函数来执行此操作,尽管我认为这并不是特别干净。 如果当前条目为零,则可以创建自己的收集器以默认为列表中的最后一个条目。像这样:

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)
  • 累加器使用新值更新了返回的对象。 (list.add)
  • 合并器用于流被拆分且需要重新加入的情况,例如并行流。 (在我们的案例中,不会调用此方法)
  • 完成是完成收藏的最终动作。在我们的例子中,只需返回ArrayList。