列表分类的最佳方法

时间:2019-05-25 12:27:49

标签: java collections

我有一个Order对象

public class Order{
  private Date orderDate;
  //other fields + getters & setters
}

和订单列表(列表<订单> )。现在,我想基于相似的orderDate(此处的相似,表示相同的年,月和日)对订单列表进行分组,并生成如下图:

Map<Date, List<Order>> orderMap = new HashMap<>();

如何在Java 8+中执行此操作?

  

请注意,我们可能有相似的日期,但时间戳不同,这意味着我们   希望基于“年,月,日”

5 个答案:

答案 0 :(得分:3)

由于Date类已被弃用并保留了实际的日期和时间,因此您可以将LocalDateTime用作Order类中的字段。然后,您可以根据LocalDate中提取的LocalDateTime对订单进行分组:

List<Order> list = new LinkedList<>();

Map<LocalDate, List<Order>> collect = list.stream()
                .collect(Collectors.groupingBy(order -> order.getOrderDate().toLocalDate()));

LocalDate保留年,月和日,而没有时间。

答案 1 :(得分:2)

您可以创建一个返回订单“相似性”的方法,例如:

public class Order{
  private Date orderDate;
  //other fields + getters & setters
  public String getStringDate(){
     SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
     return dateFormat.format(this.orderDate);
  }
}

,然后在其上分组:

Map<String, List<Order>> ordersWithSimilarOrderDate = orders.stream().collect(Collectors.groupingBy(Order::getStringDate));

答案 2 :(得分:2)

如果必须使用Date,您仍然可以通过提供时区信息将其转换为LocalDate

Map<LocalDate, List<Order>> collect =
    list.stream().collect(Collectors.groupingBy(order -> 
        order.getOrderDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()));

答案 3 :(得分:2)

tl; dr

作为Map< LocalDate , List< Order> >的密钥:

    order
    .getDate()                                  // Accessor method to retrieve your `java.util.Date` object.
    .toInstant()                                // Convert from terrible legacy class `Date` to modern `java.time.Instant` class. Both represent a moment in UTC.
    .atZone(                                    // Adjust from UTC to the time zone used by your business.
            ZoneId.of( "America/Los_Angeles" ) 
    )                                           // Remember: For any given moment, the date varies around globe by time zone, “tomorrow” in Tokyo while “yesterday” in Montréal. 
    .toLocalDate()                              // Extract the date-only portion, without time-of-day, without time zone, to use as the key in our map.

保留您的Date成员

如果您无法从可怕的orderDate类中更改java.util.Date成员变量的类型,请在 java.time 中执行业务逻辑。使用添加到旧类中的新方法在Date之间进行转换。

不要使用LocalDateTime

在其他答案中已经讨论了使用 java.time ,但是使用了错误的类:LocalDateTime。那确实是使用错误的类,因为它 not 代表一个时刻。它代表了大约26-27小时(全球时区范围)内的潜在时刻。该类具有日期和时间,但没有时区或UTC偏移量的概念。因此,如果某个LocalDateTime对象在今年1月23日下午12点举行,那么我们不知道这是在东京中午,加尔各答中午,巴黎中午还是蒙特利尔中午–都是非常不同的片刻,相隔几个小时。

要代表一个时刻,请使用以下其中一项:

  • Instant
    UTC时刻
  • OffsetDateTime
    具有相对于UTC的偏移量的时刻,大约是小时-分钟-秒
  • ZonedDateTime
    通过特定地区的人们使用的偏移量来查看的时刻,该时区的名称为Continent/Region

Table of date-time types in Java, both modern and legacy

DateInstant

java.util.Date的替换为java.time.Instant。两者都代表UTC时刻,尽管Instant的分辨率更优nanoseconds而不是毫秒。

Instant instant = orderDate.toInstant() ;

时区

从UTC调整到所需的时区。您是否要感知加州Barstow中的日期?使用America/Los_Angeles时区。应用ZoneId得到ZonedDateTimeZonedDateTimeInstant代表时间轴上的相同时刻,相同点,但是使用了不同的挂钟时间。

Continent/Region的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用2-4个字母的缩写,例如ESTIST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

请注意, java.time 类是thread-safe类,使用immutable objects设计。因此,您可以一次定义此ZoneId并将其保留以备重用,甚至可以跨线程使用。

ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, different wall-clock-time.

LocalDate

提取仅包含日期的部分,不包含日期和时区。

LocalDate ld = zdt.toLocalDate() ;  // The date as seen for this moment in Barstow, California.

使用LocalDate作为Map< LocalDate , List< Order> >的密钥。

将您的Date成员更改为Instant

  

如何在Java 8+中执行此操作?

最好将代码从可怕的旧式日期时间类迁移到现代的 java.time 类。 Sun,Oracle和JCP社区在采用JSR 310时放弃了旧的类。所以,您应该-他们真的那么糟糕。

通常最好在后端上以UTC工作,思考,存储和交换日期时间值。仅将时区用于(a)向用户演示,(b)如果业务逻辑需要,则为时区。

public class Order{
  private Instant whenOrdered ;
  //other fields + getters & setters
}

应用与上述相同的时区调整逻辑,以获取LocalDate作为Map< LocalDate , List< Order > >的密钥。

在用户喜欢的任何时区中向用户显示Instant。让 java.time automatically localize显示文本。

Locale locale = new Locale( "fr" , "TN" ) ;      // French in Tunisia.
ZoneId userZone = ZoneId.of( "Africa/Tunis" ) ;  // Or ZoneId.systemDefault() to get the JVM’s current default time zone (appropriate to a client machine, but not a server machine). 
String outputForDisplay = 
    order
    .whenOrdered
    .atZone( userZone )                          // Adjust from UTC to time zone, going from `Instant` to `ZonedDateTime`. 
    .format(                                     // Generate text representing the value inside our `ZonedDateTime` object.
        DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG )  // Let java.time automatically localize, rather than hard-code a formatting pattern.
    ) 
;

如果您只关心日期,而不关心时间,又不在乎跨时区确定日期的模糊性(您是一家小型企业),请使用{{1} }。

LocalDate

关于 java.time

java.time框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

目前位于Joda-Timemaintenance mode项目建议迁移到java.time类。

您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要public class Order{ private LocalDate dateOrdered ; //other fields + getters & setters } 类。

在哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

答案 4 :(得分:1)

我真的不支持使用流进行所有操作,但是您可以做到 用硬转换的方式。

 List<Order> orders = ... your list ...
 Map<Date, List<Order>> orderMap = orders.stream().collect(Collectors.groupingBy(order->{
      //Converting from Date to Instant
      Instant dateInstant = order.getOrderDate().toInstant();
      //Converting Instant to LocalDateTime and setting it to the start of the day
      LocalDateTime dateTime = dateInstant.atZone(ZoneId.systemDefault()).toLocalDate().atStartOfDay();
      //Converting it back to an instant
      Instant startOfDayInstant = dateTime.atZone(ZoneId.systemDefault()).toInstant();
      //Returning the util date
      return Date.from(startOfDayInstant);
    }));

这是在您需要使用日期的情况下。否则,我支持michalk的答案