【问题标题】:Extend output of stream with groupingBy & summingInt in Java8在 Java8 中使用 groupingBy 和 summingInt 扩展流的输出
【发布时间】:2018-09-10 14:54:01
【问题描述】:

我有一个话题:

@Entity
public class Topic {
    @Id
    private int id;
    private LocalDate date;
    private String name;
    private int points;
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
}

我正在通过 spring data jpa 方法获取给定日期的主题列表:

列出主题 = topicRepository.findByDateBetween(begin, end); 输出有例如:

Topic(id=1, date="2018-01-01", name="Java examples", User(...), 12)
Topic(id=2, date="2018-02-02", name="Java examples", User(...), 34)
Topic(id=3, date="2018-02-02", name="Java examples", User(...), 56)
Topic(id=4, date="2018-03-03", name="Java examples", User(...), 78)
Topic(id=5, date="2018-03-03", name="Java examples", User(...), 90)

我试图实现的是将我的结果输出过滤为(如果 date && User 是相同的添加点)

Topic(id=1, date="2018-01-01", name="Java examples", User(...), 12)
Topic(id=2, date="2018-02-02", name="Java examples", User(...), 90)
Topic(id=4, date="2018-03-03", name="Java examples", User(...), 168)

我的实际解决方案返回地图,其中日期为键,总和点为值,但我需要在输出中提供更多数据,如用户或名称。

return topics.stream()
        .collect(Collectors.groupingBy(Topic::getDate,
                Collectors.summingInt(Topic::getPoints)));

也许有另一种方法可以代替 map return created dto 那种情况?例如

@Data
public class ResultDto {
    private LocalDate date;
    private String name;
    private int points;
    private User user;
}

【问题讨论】:

  • 用户也需要相同吗?我的意思是,您是否需要按日期和用户分组,也就是说,如果 2018-02-02 有 2 个用户,user 1 有 10 和 5,user 2 有 20 和 8,你最终会有两个组, (2018-02-02, user 1) 15 和 (2018-02-02, user 2) 28?
  • 是的,我举了一个例子,当用户相同时会发生什么。但你是对的,它会像你写的那样。
  • 好的,名字呢?你也需要按那个分组吗?如果不是,它不应该是 ResultDto 的属性(或者这个 dto 应该有一个组成该组的所有名称的列表)。
  • 您的权利。它不应该。但我们可以假设,我们也需要那个。

标签: java-8 java-stream


【解决方案1】:

按字段子集进行分组的一种简单方法是将TreeMap 与自定义Comparator 结合使用。假设你要定义

Comparator<Topic> byDateAndUser = Comparator.comparing(Topic::getDate)
            .thenComparing(t -> t.getUser().getUserId());

Map<Topic,...> map = new TreeMap<>(byDateAndUser);

生成的映射将使用提供的比较器而不是 equals 来确定相等性,因此会将具有相同日期和用户的所有主题视为相同。

TreeMap 的此功能允许您计算主题到总点数的映射,并且对于每个日期/用户组合,它将只包含一个条目:

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summingInt;

Comparator<Topic> byDateAndUser = Comparator.comparing(Topic::getDate)
            .thenComparing(t -> t.getUser().getUserId());

Map<Topic, Integer> pointTotals = topics.stream()
        .collect(groupingBy(
                topic -> topic, 
                () -> new TreeMap<>(byDateAndUser), 
                summingInt(Topic::getPoints)
        ));

【讨论】:

    【解决方案2】:

    假设User 实现hashCodeequals 一致,您可以分组到日期/用户复合键和ResultDto 值的映射。为此,您需要两个操作:一个映射to,另一个为每个组聚合点。 (您可以将这些操作放在ResultDto 或实用程序类等中,这里我假设第一个在Mapper 实用程序类中,第二个在ResultDto 中):

    public final class Mapper {
    
        private Mapper() { }
    
        public static ResultDto fromTopic(Topic topic) {
            ResultDto result = new ResultDto();
            result.setDate(topic.getDate());
            result.setName(topic.getName());
            result.setPoints(topic.getPoints());
            result.setUser(topic.getUser());
            return result;
        }
    }
    

    ResultDto:

    public ResultDto merge(ResultDto another) {
        this.points += another.points;
        return this;
    }
    

    请注意,我正在分配在Mapper.fromTopic 映射操作中找到的第一个Topic.name。我假设这是您的示例中的不一致之处,如果您在实际场景中使用此方法,请考虑到这一点。

    现在我们可以按日期/用户流式传输主题和分组:

    Map<List<Object>, ResultDto> groups = topics.stream()
        .collect(Collectors.toMap(
            topic -> Arrays.asList(topic.getDate, topic.getUser()), // Java 9: List.of
            Mapper::fromTopic,
            ResultDto::merge));
    

    这会收集到一个映射,其键是List,它的第一个元素是日期,第二个元素是用户。这些 2 元素列表对以后的使用不是很有用,这里我们只使用它们来创建复合键并通过该键对主题进行分组。映射的值是一个 ResultDto 实例,它最初是从主题创建的,然后与属于同一组(相同日期和用户)的其他主题合并。在这个ResultDto.merge 操作中,points 被求和。

    你需要的结果是地图的值:

    Collection<ResultDto> results = groups.values(); // or new ArrayList<>(groups.values())
    

    编辑:这里有一个更简洁的没有流的变体:

    Map<List<Object>, ResultDto> groups = new HashMap<>();
    
    topics.forEach(topic -> groups.merge(
        List.of(topic.getDate(), topic.getUser()), // Java 8: Arrays.asList
        Mapper.fromTopic(topic),
        ResultDto::merge));
    
    Collection<ResultDto> results = groups.values();
    

    【讨论】:

    • 谢谢@Federico。不幸的是,我需要离开。我会检查那个答案并尽快接受(可能明天早上)。再次感谢,祝您有美好的一天。
    猜你喜欢
    • 2018-05-31
    • 2015-04-09
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2019-08-17
    • 1970-01-01
    • 2011-04-14
    • 1970-01-01
    相关资源
    最近更新 更多