Как правильно работать с методом reduce в Stream API?

Всем привет, подскажите, как переписать использования метода reduce. Результат, которого хочется добиться: Задать в методе reduce значение по умолчанию accumulator, например new ArrayList<>() и чтобы, на каждой итерации в этот ArrayList я добавлял currentValue.

package com.example.demo;

import java.util.ArrayList;
import java.util.List;

class DataObj {

    private String id;

    private String title;

    public DataObj(String id, String title) {
        this.id = id;
        this.title = title;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "DataObj{" +
                "id='" + id + '\'' +
                ", title='" + title + '\'' +
                '}';
    }
}

public class Test {
    public static void main(String[] args) {
        List<DataObj> dataObjs = new ArrayList<>();

        DataObj dataObj1 = new DataObj("1", "one");
        DataObj dataObj2 = new DataObj("2", "two");
        DataObj dataObj3 = new DataObj("3", "three");
        DataObj dataObj10 = new DataObj("10", "ten");

        dataObjs.add(dataObj1);
        dataObjs.add(dataObj2);
        dataObjs.add(dataObj3);
        dataObjs.add(dataObj10);

        System.out.println("dataObjs " + dataObjs);

//        List<DataObj> output = new ArrayList<>();

        DataObj result = dataObjs.stream().reduce(dataObjs.get(0), (accumulator, currentValue) -> {
//            System.out.println("accumulator " + accumulator);

            return accumulator;

        });

        System.out.println("result " + result);
    }
}

Ответы (1 шт):

Автор решения: Sergey

Можно попробовать так.

ArrayList<DataObj> r = dataObjs.stream().reduce(
    new ArrayList<>(), 
    (acc, curr) -> { acc.add(curr); return acc; }, 
    (acc1, acc2) -> { acc1.addAll(acc2); return acc1; }
);

Но это если уверены, что обработка стрима не распараллеливается.
Потому, что редукция каждого куска должна начинаться с одного и того же начального значения identity (new ArrayList<>()).
Но в таком аккумуляторе он сразу же изменяется. И вообще каждый поток будет вести запись в один список в разнобой. А потом ещё окажется, что этот список будет объединяться сам с собой. Что же получится в результате?

Правильно сделать наверное как-то так.

ArrayList<DataObj> r = dataObjs.stream().reduce(
    new ArrayList<>(), 
    (acc, curr) -> {
        if (acc.isEmpty()) {
            acc = new ArrayList<>();
        } 
        acc.add(curr); 
        return acc; 
    }, 
    (acc1, acc2) -> { 
        if (acc1.isEmpty()) return acc2;
        acc1.addAll(acc2); 
        return acc1; 
    }
);

Т.е. в аккумуляторе проверяется, что acc является identity. identity пустой, acc планируется заполнять. Поэтому identity легко опознать по isEmpty. И вместо identity для дальнейшей обработки подсунуть новый список.

Вторая функция, если кто не знает, используется для сбора "параллельных" кусков в одно целое. Тоже модифицирована, чтобы случайно не испортить identity.

И вообще у стрима для сбора коллекция есть спец.методы collect.

→ Ссылка