Поток Java, использующий предыдущий элемент в лямбде Foreach

у меня есть список чисел, с некоторыми 0s внутри. С 0 означает недопустимую меру в моей ситуации, мне нужно изменить 0 valued элемент с первым элементом не 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 и лямбда Функции?

поскольку я знаю, что не должен изменять источник потока в лямбде foreach, на самом деле список является списком объектов, и я не изменяю элемент списка, но я просто назначаю новые значения.

это было мое первое решение

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

но я также читаю здесь

лучше всего, если лямбды, переданные потоковым операциям, полностью боковые эффект бесплатно. то есть, что они не мутируют любой heapbased государство или выполните любой ввод-вывод во время их выполнения.

это то, что worryng меня

данные секционированы, нет никакой гарантии, что когда данный элемент обработано, все элементы, предшествующие этому элементу, уже обработаны.

может ли это решение привести к недопустимым результатам, возможно, когда количество элементов в списке велико? ? Событие, если я не использую parallelStream() ?

4 ответов


вы можете изменить состояние объектов внутри потока. Но вы не можете изменить источник данных.

проблема такая же, как в классическом переборе.

использовать forEachOrdered() для выполнения

действие для каждого элемента этого потока, в порядке встречи потока, если поток имеет определенный порядок встречи

Если вы называете

result.stream.forEachOrdered(...)

все элементы будут обрабатываться последовательно, в порядке.

для последовательные потоки forEach Кажется, уважает порядок.


лучше всего, если лямбды, переданные потоковым операциям, полностью боковые эффект бесплатно. то есть, что они не мутируют какое-либо состояние heapbased или выполните любой ввод-вывод во время их выполнения.

ваше решение на самом деле было!--6-->побочный эффект, он изменяет список источников на список ресурсов. Чтобы этого избежать, вам нужен оператор карты и преобразуйте свой поток в коллекцию. Поскольку вы не можете получить доступ к предыдущему элементу, состояние должно быть сохранено снаружи, в последнем поле. Для краткости я использовал 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]

использование потока для вашей проблемы - не лучшее решение, если вам не нужны дополнительные операции, такие как фильтр, другие операции карты или сортировка.


во-первых, вы не должны мутировать состояние внутри лямбда. Тем не менее, вы можете использовать пользовательский список extending 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());
    }
}

обратите внимание, что это не тестируется с помощью параллельного потока. На самом деле, я почти уверен, что это не сработает в параллельный.


есть способ сделать это только с потоковыми функциями, хотя, на мой взгляд, это не особенно чисто. Вы можете создать свой собственный коллектор по умолчанию для последней записи в списке, если текущая запись равна нулю. Что-то вроде этого:--4-->

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 С)
  • аккумулятор обновил возвращаемый объект новым значением. (список.добавить)
  • объединитель используется в случае, когда поток был разделен и должен быть присоединен, например, в параллельном потоке. (Это не будет называться в нашем случае)
  • The finished является заключительным действием для завершения коллекции. В нашем случае просто верните список ArrayList.