我有一个数字列表,里面有一些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()
会发生什么事?
答案 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
使用供应商,累加器,合并器,修整器模式。