我有一个Order对象
public class Order{
private Date orderDate;
//other fields + getters & setters
}
和订单列表(列表<订单> )。现在,我想基于相似的orderDate(此处的相似,表示相同的年,月和日)对订单列表进行分组,并生成如下图:
Map<Date, List<Order>> orderMap = new HashMap<>();
如何在Java 8+中执行此操作?
请注意,我们可能有相似的日期,但时间戳不同,这意味着我们 希望基于“年,月,日”
答案 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)
作为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
OffsetDateTime
ZonedDateTime
Continent/Region
Date
➙Instant
java.util.Date
的替换为java.time.Instant
。两者都代表UTC时刻,尽管Instant
的分辨率更优nanoseconds而不是毫秒。
Instant instant = orderDate.toInstant() ;
从UTC调整到所需的时区。您是否要感知加州Barstow中的日期?使用America/Los_Angeles
时区。应用ZoneId
得到ZonedDateTime
。 ZonedDateTime
与Instant
代表时间轴上的相同时刻,相同点,但是使用了不同的挂钟时间。
以Continent/Region
的格式指定proper time zone name,例如America/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用2-4个字母的缩写,例如EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
请注意, 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 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和SimpleDateFormat
。
要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310。
目前位于Joda-Time的maintenance 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中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如Interval
,YearWeek
,YearQuarter
和more。
答案 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的答案