![](/img/trans.png)
[英]Java-Stream - How to apply sorting while using Collector 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_MONTH
, MONTH
, YEAR
之外的所有其他字段设置为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.