簡體   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