Группа потоков Java 8

У меня есть коллекция pojos:

public class Foo {
    String name;
    String date;
    int count;
}

Мне нужно перебрать коллекцию, groupBy Foos по имени и сумме, а затем создать новую коллекцию с pojos с суммированным счетчиком.

Вот как я это делаю сейчас:

    List<Foo> foosToSum = ...

    Map<String, List<Foo>> foosGroupedByName = foosToSum.stream()
            .collect(Collectors.groupingBy(Foo::getName));

    List<Foo> groupedFoos = foosGroupedByName.keySet().stream().map(name -> {
        int totalCount = 0;
        String date = "";
        for(Foo foo: foosGroupedByName.get(name)) {
            totalCount += foo.getCount();
            date = foo.getDate() //last is used
        }
        return new Foo(name, date, totalCount);
    }).collect(Collectors.toList());

Есть ли более красивый способ сделать это с помощью потоков?

ОБНОВИТЬ Спасибо всем за помощь. Все ответы были отличными. Я решил создать функцию слияния в pojo.

Окончательное решение выглядит так:

Collection<Foo> groupedFoos = foosToSum.stream()
                    .collect(Collectors.toMap(Foo::getName, Function.identity(), Foo::merge))
                    .values();
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
0
950
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Да, вы можете использовать нижний коллектор в вашем groupingBy для немедленного суммирования подсчетов. После этого транслируйте карту и сопоставьте ее с Foos.

foosToSum.stream()
         .collect(Collectors.groupingBy(Foo::getName,
                                        Collectors.summingInt(Foo::getCount)))
         .entrySet()
         .stream()
         .map(entry -> new Foo(entry.getKey(), null, entry.getValue()))
         .collect(Collectors.toList());

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

foosToSum.stream()
         .collect(Collectors.groupingBy(Foo::getName,
                                        Collectors.reducing(new Foo(),
                                                            (foo1, foo2) -> new Foo(foo1.getName(), null, foo1.getCount() + foo2.getCount()))))
         .values();

Уменьшая Foos вместо int, мы учитываем имя и можем сразу же суммировать в Foo.

Отличное решение. Но мне нужно как-то получить поле последней даты, сгруппированное по имени Foos. Моя ошибка, я написал, что это не важно, но это важно.

Kirill Bazarov 13.04.2018 13:47

Что ж, вы можете попробовать просто использовать foo2.getDate() вместо null. Но тогда вы полагаетесь на то, что foos все еще заказывают. Если вы хотите убедиться, вы можете использовать что-то вроде getLater(foo1.getDate(), foo2.getDate()). Как сказал Аомине в другом ответе, вы можете поместить всю эту логику в класс Foo и написать ...reducing(new Foo(), Foo::merge)

Malte Hartwig 13.04.2018 13:54
Ответ принят как подходящий

Вы можете сделать это либо с помощью groupingBy, либо с помощью сборщика toMap, вопрос о том, что использовать, остается спорным, поэтому я позволю вам выбрать тот, который вам больше нравится.

Для лучшей читаемости я бы создал функцию слияния в Foo и спрятал всю логику слияния внутри нее.

Это также означает лучшую ремонтопригодность, поскольку чем сложнее становится слияние, вам нужно только изменить одно место, и это метод merge, а не запрос потока.

например

public Foo merge(Foo another){
     this.count += another.getCount();
     /* further merging if needed...*/
     return this;
}

Теперь вы можете:

Collection<Foo> resultSet = foosToSum.stream()
            .collect(Collectors.toMap(Foo::getName,
                    Function.identity(), Foo::merge)).values();

Обратите внимание, что указанная выше функция слияния изменяет объекты в исходной коллекции, если вместо этого вы хотите сохранить ее неизменной, вы можете создать новые Foo следующим образом:

public Foo merge(Foo another){
      return new Foo(this.getName(), null, this.getCount() + another.getCount());
}

Кроме того, если по какой-то причине вам явно требуется List<Foo> вместо Collection<Foo>, то это можно сделать с помощью конструктора копирования ArrayList.

List<Foo> resultList = new ArrayList<>(resultSet);

Обновлять

Как упоминал @Federico в комментариях, последняя функция слияния выше стоит дорого, поскольку создает ненужные объекты, которых можно было бы избежать. Итак, как он предложил, более удобной альтернативой является использование первой функции слияния, которую я показал выше, а затем изменение запроса к потоку на это:

Collection<Foo> resultSet = foosToSum.stream()
                .collect(Collectors.toMap(Foo::getName,
                        f -> new Foo(f.getName(), null, f.getCount()), Foo::merge))
                .values();

Привет, Аомине! Просто комментарий ... Если вы используете Function.identity() в качестве функции преобразования значений, правильно, что элементы исходной коллекции изменены. Итак, вы предлагаете создать новые элементы Foo при слиянии. Хотя это работает и правильно, оно создает ненужные объекты. Вы можете создать копию в функции сопоставления значений (просто используйте конструктор копирования), а затем объединить эту копию, как в исходном методе merge.

fps 13.04.2018 14:57

Привет, @FedericoPeraltaSchaffner. Я видел ваш комментарий ранее, но не мог ответить. сейчас я :) . в любом случае, вы правы, и я соответствующим образом обновил свой ответ. Спасибо за ответ.

Ousmane D. 13.04.2018 16:14

Привет, Аомине, здорово, ты обновил свой уже принятый ответ;) Я уже проголосовал заранее :)

fps 13.04.2018 16:18

@FedericoPeraltaSchaffner на самом деле, глядя на мой код еще раз, он создавал новые объекты только тогда, когда происходит столкновение ключей, и в случае, если столкновения нет, он все равно будет поддерживать этот конкретный объект из источника. так что вы не только улучшили мой код, но и решили тонкую проблему :).

Ousmane D. 13.04.2018 16:18

Аомине, да, и в этом вы правы. Я этого не заметил. Тогда хорошо. Мы убили двух зайцев одним выстрелом ...

fps 13.04.2018 16:20

@FedericoPeraltaSchaffner, зачем создавать новые объекты, если мы можем видоизменить и повторно использовать старые. Таким образом, функция слияния будет выглядеть как исходная, а Function.identity() останется.

Kirill Bazarov 13.04.2018 17:21

@KirillBazarov, если можно изменять существующие объекты, вам не нужно создавать новые объекты. Я предложил создавать новые объекты только в том случае, если вы не хотите изменять существующие объекты.

Ousmane D. 13.04.2018 17:23

Хорошо, теперь я понял. Спасибо.

Kirill Bazarov 13.04.2018 17:23

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

Kirill Bazarov 13.04.2018 17:24

Я не понимаю, чем ваша вторая (обновленная) версия лучше первой. Я чувствую, что только во втором случае вы будете каждый раз создавать один объект Foo (valueMapper в toMap) - в то время как в первом вы используете Function.identity и выполняете слияние Только при конфликте ключей. Я что-то пропустил?

user7 09.05.2018 06:24

@ user7 Дело в том, что функция слияния лучше в обновленной версии. Но что использовать, Function.identity или новый объект - решать вам, в зависимости от того, нужны ли вам новые объекты или изменить существующие. Первая функция слияния создаст ненужные объекты, если, например, существует более одного конфликта для одного и того же ключа.

Kirill Bazarov 11.05.2018 14:24

Другие вопросы по теме