繁体   English   中英

如何使用Java Stream API(groupingBy收集器)对列表中的值进行分组?

[英]How to group values from a list with Java Stream API (groupingBy collector)?

我有Entry对象的列表。 Entry是:

class Entry {
   private final Date date;
   private final String value;

   // constructor
   // getters
}

我需要按天对这些条目进行分组。 例如,

2011-03-21 09:00 VALUE1
2011-03-21 09:00 VALUE2
2011-03-22 14:00 VALUE3
2011-03-22 16:00 VALUE4
2011-03-21 16:00 VALUE5

应分组:

2011-03-21
    VALUE1
    VALUE2
    VALUE5

2011-03-22
    VALUE3
    VALUE4

我想获取一个Map<Date, List<Entry>> 如何使用Stream API(groupingBy收集器)获取此信息?

我在下面的尝试:

final Map<Date, List<Entry>> entries =
        list.stream().collect(Collectors.groupingBy(request -> {
        final Calendar ogirinal = Calendar.getInstance();
        ogirinal.setTime(request.getDate());

        final Calendar cal = Calendar.getInstance();
        cal.set(Calendar.DAY_OF_MONTH, ogirinal.get(Calendar.DAY_OF_MONTH));
        cal.set(Calendar.MONTH, ogirinal.get(Calendar.MONTH));
        cal.set(Calendar.YEAR, ogirinal.get(Calendar.YEAR));

        return cal.getTime();
    }));

输出:

2011-03-21
    VALUE1
2011-03-21
    VALUE2
2011-03-22
    VALUE3
    VALUE4
2011-03-21
    VALUE5

您必须截断日期值,因为它们可能在毫秒之间有所不同:

import static java.time.temporal.ChronoUnit.DAYS;

final Map<Instant, List<Entry>> entries =
    list.stream().collect(Collectors.groupingBy(request -> 
        request.getDate().toInstant().truncatedTo(DAYS)));

同样,当您可以使用LocalDateTime时,也不明白为什么使用java.util.Date 但是我的尝试在这里:

Map<Date, List<Entry>> entries = list.stream().collect(Collectors.groupingBy(e ->
    // easier way to truncate the date
    Date.from(e.getDate().toInstant().truncatedTo(ChronoUnit.DAYS)))
);

演示

您的方法几乎是正确的方法,但是您输入的内容归为不同的组,因为返回的Calendar对象的时间戳都略有不同。 这是因为您没有将小时/分钟/ ...设置为0。只有当两个日历巧合的时间相同时(例如,由于计时器不正确),两个条目才会在同一组中结束。

使用类似这样的东西来分组:

LocalDate.fromDateFields(request.getDate());

LocalDate使创建仅日期的时间戳比Calendar容易得多。 该代码段使用joda time的LocalDate ,但是Java本身仅稍长一些。

如果要按日期(日,月和年)分组,则可以舍弃时间(小时,分钟,秒)。 使用Java 8时,只需将java.util.Date转换为java.time.LocalDate

Map<LocalDate, List<Entry>> entries = list.stream()
    .collect(Collectors.groupingBy(request ->
         request.getDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()));

仅提供一些背景信息: Date表示特定的时间点,即自Unix纪元以来的毫秒数 ,而LocalDate仅表示日/月/年的日期,没有任何时区的概念。

为了将Date正确地转换为LocalDate ,我将其设置为JVM默认时区(使用toInstant().atZone() ),然后仅获取本地部分( toLocalDate() )。

您还可以简化设计,如果可能的话,将Entry类更改为具有LocalDate字段。 您正在使用Java 8,除非有充分的理由使用旧的API(“旧版代码”,“我的老板不想要”等),否则最好开始使用java.time

你快到了。 您遇到了这个问题,因为在启动Calendar cal = Calendar.getInstance()对象时,还设置了分钟,秒,小时和所有其他字段,这在分组中造成了麻烦。 (将这两个时间戳分组在一起实际上并不相同 。)

您需要清除所有其他字段(除了要设置的字段),以使它们本质上相同。

您需要将除DAY_OF_MONTHMONTHYEAR之外的所有其他字段设置为0。将Calender.clear()用作相同字段。

final Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(Calendar.DAY_OF_MONTH, ogirinal.get(Calendar.DAY_OF_MONTH));
cal.set(Calendar.MONTH, ogirinal.get(Calendar.MONTH));
cal.set(Calendar.YEAR, ogirinal.get(Calendar.YEAR));

这应该可以解决问题。

已经有很多好的和正确的答案。 我仍然想贡献我的力量。 通过声明日期字段(例如LocalDateTime ,全面使用Java.time(现代的Java日期和时间API)。 您可能需要同时更改其名称:

private final LocalDateTime dateTime;

使Entry类适合于仅获取日期的便捷方法:

public LocalDate getDateWithoutTimeOfDay() {
    return dateTime.toLocalDate();
}

现在剩下的事情真的很简单了:

    final Map<LocalDate, List<Entry>> entries =
            list.stream().collect(Collectors.groupingBy(Entry::getDateWithoutTimeOfDay));

    // print the result
    entries.entrySet().forEach(e -> {
        System.out.println(e.getKey());
        e.getValue().forEach(v -> System.out.println("    " + v.getValue()));
        System.out.println();
    });

打印:

2011-03-22
    VALUE3
    VALUE4

2011-03-21
    VALUE1
    VALUE2
    VALUE5

由于未对从分组返回的地图进行排序,因此无法保证打印输出中日期的顺序,但是分组是您想要的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM