簡體   English   中英

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

[英]Java Stream Using Previous Element in Foreach 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使用經典的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);
        }
});

但我也在這里讀到

最好將lambdas傳遞給流操作完全沒有副作用。 也就是說,它們不會在執行期間改變任何基於堆的狀態或執行任何I / O.

這讓我很擔心

數據是分區的,不能保證在處理給定元素時,已經處理了該元素之前的所有元素。

這個解決方案是否會產生無效結果,可能是當列表中的元素數量很高時? 如果我不使用parallelStream()

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

除非您需要一些額外的操作,如過濾器,其他地圖操作或排序,否則使用流來解決您的問題不是最佳解決方案。

有一種方法只使用流函數來實現這一點,盡管在我看來它並不是特別干凈。 如果當前條目為零,您可以創建自己的收集器以默認為列表中的最后一個條目。 像這樣的東西:

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。

您可以修改流內的對象狀態。 但是你無法修改數據源狀態。

問題與經典迭代中的問題相同。

使用forEachOrdered()執行

如果流具有已定義的遭遇順序,則按流的遭遇順序對此流的每個元素執行操作

如果你打電話

result.stream.forEachOrdered(...)

所有元素將按順序依次處理。

對於順序流forEach似乎尊重順序。

首先,你不應該在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());
    }
}

請注意,這不是使用並行流測試的。 事實上,我幾乎可以肯定它不會並行工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM