Объединить список карт в одну карту путем суммирования значений

У меня есть класс Значения:

public class Values {
    private int count;
    private int values;
}

И список нескольких карт типа Map<String, Values>

Map<String, Values> map1 = new HashMap<>();
        map1 .put("aaa", new Values(1, 10));
        map1 .put("bbb", new Values(5, 50));
        map1 .put("ccc", new Values(2, 30));

Map<String, Values> map2= new HashMap<>();
        map2.put("aaa", new Values(2, 20));
        map2.put("bbb", new Values(3, 50));
        map2.put("ccc", new Values(3, 10));

List<Map<String, Values>> list = Arrays.asList(map1, map2);

Там может быть любое количество карт и любое количество записей внутри карт, но ключи для карт всегда одинаковы, только значения могут отличаться. Мой пример содержит только 2 карты и только 3 записи для каждой карты для ясности.

Я хочу получить одну карту с теми же ключами и объектами Values, как сумма каждого "количества" и каждого "значения" в объектах, например:

{
        aaa= {
            count = 3,
            value = 30
        },
        bbb= {
            count = 8,
            value = 100
        },
        ccc= {
            count = 6,
            value = 40
        }
    }

Я пытаюсь добиться этого с помощью Streams API, но я застрял:

public static Map<String, Values> mergeMaps(List<Map<String, Values>> maps) {
        return maps.stream()
                .flatMap(m -> m.entrySet().stream()) ...?
    }

Как я могу сгруппировать каждую запись по их ключам и сложить каждый счетчик и каждое значение в одну карту?

Заранее спасибо.

Ответ 1

Вы можете собирать записи вашего Stream с помощью сборщика toMap, с функцией слияния.

public static Map<String, Values> mergeMaps(List<Map<String, Values>> maps) {
    return maps.stream()
               .flatMap(m -> m.entrySet().stream())
               .collect(Collectors.toMap(Map.Entry::getKey,
                                         Map.Entry::getValue,
                                         (v1,v2) -> new Values(v1,v2)));
}

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

Конечно, вы можете написать функцию слияния без этого конструктора. Например:

(v1,v2) -> new Values(v1.getCount()+v2.getCount(),v1.getValue()+v2.getValue())

Ответ 2

Еще одно решение с группировкой:

Map<String, Optional<Values>> collect = list.stream()
            .flatMap(map -> map.entrySet().stream())
            .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue,
                    reducing((v1, v2) -> new Values(v1.count + v2.count, v1.values + v2.values)))));

Примечание: значения этой карты являются Optional<Values>. Если у вас есть null значение на одной из ваших исходных карт, например map2.put("ddd", null); это позволяет избежать NullPointerException и вернуть Optional.empty