I have list of Entry
objects. Entry
is a:
class Entry {
private final Date date;
private final String value;
// constructor
// getters
}
I need to group these entries by day . For example,
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
Should be grouped:
2011-03-21
VALUE1
VALUE2
VALUE5
2011-03-22
VALUE3
VALUE4
I want to get a Map<Date, List<Entry>>
. How can I get this using the Stream API (groupingBy collector)?
My attempt below:
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();
}));
Output:
2011-03-21
VALUE1
2011-03-21
VALUE2
2011-03-22
VALUE3
VALUE4
2011-03-21
VALUE5
You have to truncate the date values, as they may be different up to the millisecond:
import static java.time.temporal.ChronoUnit.DAYS;
final Map<Instant, List<Entry>> entries =
list.stream().collect(Collectors.groupingBy(request ->
request.getDate().toInstant().truncatedTo(DAYS)));
Again don't understand why you are using java.util.Date
when you can use LocalDateTime
for example. But here goes my attempt:
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)))
);
Your approach is pretty much the right way to go, but your entries end up in different groups because the Calendar
objects you return all have slightly different timestamps. This is because you do not set hour/minute/... to 0. Only when two of the calendars have the same time by coincidence (due to timer inaccuracies for example) two entries will end up in the same group.
Use something like this to group instead:
LocalDate.fromDateFields(request.getDate());
LocalDate
makes creating date-only timestamps much easier than Calendar
. This snippet uses joda time's LocalDate
, but Java's own is only slightly longer.
If you want to group by date (day, month and year), you can discard the time (hour, minutes, seconds). As you're using Java 8, just convert the java.util.Date
to a java.time.LocalDate
:
Map<LocalDate, List<Entry>> entries = list.stream()
.collect(Collectors.groupingBy(request ->
request.getDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()));
Just some background: a Date
represents a specific point in time, the number of milliseconds since Unix epoch , while a LocalDate
represents only a day/month/year date, without any notion of timezone.
To properly convert a Date
to a LocalDate
, I set it to the JVM default timezone (using toInstant().atZone()
) and then get only the local part ( toLocalDate()
).
You could also make your design simpler, changing the Entry
class to have a LocalDate
field, if possible. You're using Java 8, and unless you have a good reason to use the old API ("legacy code", "my boss doesn't want", etc), it's better to start using java.time
stuff.
You are almost there. You are facing the issue because, while initiating the Calendar cal = Calendar.getInstance()
object, the minutes, seconds, hours, and all other fields are also getting set which is causing trouble in the grouping. (The two timestamps aren't actually same for them to be grouped together.)
You need to clear all the other fields except the ones that you are setting so that they are essentially same.
You need to set all other fields except DAY_OF_MONTH
, MONTH
, YEAR
to 0. Use Calender.clear()
for the same.
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));
This should clear the problem.
There are many good and correct answers already. Still I should like to contribute my take. Take the full step into using java.time, the modern Java date and time API, by declaring your date field for example a LocalDateTime
. You will probably want to change its name at the same time:
private final LocalDateTime dateTime;
Fit the Entry
class with a convenience method for getting the date only:
public LocalDate getDateWithoutTimeOfDay() {
return dateTime.toLocalDate();
}
Now the rest is really simple:
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();
});
This printed:
2011-03-22
VALUE3
VALUE4
2011-03-21
VALUE1
VALUE2
VALUE5
Since the map returned from the grouping isn't sorted, there's no guarantee about the order of the dates in the printout, but the grouping is as you desired.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.