[英]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
使用供應商,累加器,組合器,修整器模式。
您可以修改流內的對象狀態。 但是你無法修改數據源狀態。
問題與經典迭代中的問題相同。
使用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.