[英]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.