1. 概述
在这个教程中,你将学习如何重构你的代码,以利用Java 8引入的新日期时间API。
2. 新API概览
在Java中处理日期曾经很复杂。旧的JDK提供的日期库仅包含三个类:java.util.Date
、java.util.Calendar
和java.util.TimeZone
。这些仅适用于最基础的任务。对于任何稍微复杂一些的情况,开发人员要么需要使用第三方库,要么就得编写大量的自定义代码。
Java 8引入了一个全新的日期时间API(java.util.time.*
),它基于流行的Java库JodaTime进行设计,极大地简化了日期和时间处理,并修复了旧日期库的许多不足。
2.1. API清晰性
新API的第一个优点是清晰性——API简洁明了,易于理解。它不像旧库那样存在很多不一致性,例如日历月份是零索引的,而一周中的天数是一索引的。
2.2. API灵活性
另一个优点是灵活性——处理多种时间表示形式。旧日期库只包含一个时间表示类——java.util.Date
,尽管其名称如此,但实际上它是时间戳,仅存储自Unix纪元以来经过的毫秒数。
新API有许多不同的时间表示形式,每个都适合不同的用例:
-
Instant
—— 表示时间点(时间戳) -
LocalDate
—— 表示日期(年、月、日) -
LocalDateTime
—— 类似于LocalDate
,但具有纳秒精度的时间 -
OffsetDateTime
—— 类似于LocalDateTime
,但带有时区偏移量 -
LocalTime
—— 具有纳秒精度但不包含日期信息的时间 -
ZonedDateTime
—— 类似于OffsetDateTime
,但包含时区ID -
OffsetLocalTime
—— 类似于LocalTime
,但带有时区偏移量 -
MonthDay
—— 月份和日期,不包含年份或时间 -
YearMonth
—— 月份和年份,不包含日期或时间 -
Duration
—— 以秒、分钟和小时表示的时间量,具有纳秒精度 -
Period
—— 以天、月和年表示的时间量
2.3. 不变性和线程安全
另一个优点是Java 8日期时间API中的所有时间表示都是不可变的,因此是线程安全的。
所有修改方法都会返回一个新的副本,而不是修改原始对象的状态。
像java.util.Date
这样的旧类不是线程安全的,可能会引入微妙的并发问题。
2.4. 方法链式调用
所有修改方法都可以串联在一起,允许在一行代码中实现复杂的转换。
ZonedDateTime nextFriday = LocalDateTime.now()
.plusHours(1)
.with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
.atZone(ZoneId.of("PST"));
3. 示例
下面的示例将演示如何使用旧API和新API执行常见任务。
获取当前时间
// Old
Date now = new Date();
// New
ZonedDateTime now = ZonedDateTime.now();
表示特定时间
// Old
Date birthDay = new GregorianCalendar(1990, Calendar.DECEMBER, 15).getTime();
// New
LocalDate birthDay = LocalDate.of(1990, Month.DECEMBER, 15);
提取特定字段
// Old
int month = new GregorianCalendar().get(Calendar.MONTH);
// New
Month month = LocalDateTime.now().getMonth();
添加和减去时间
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.add(Calendar.HOUR_OF_DAY, -5);
Date fiveHoursBefore = calendar.getTime();
// New
LocalDateTime fiveHoursBefore = LocalDateTime.now().minusHours(5);
更改特定字段
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.set(Calendar.MONTH, Calendar.JUNE);
Date inJune = calendar.getTime();
// New
LocalDateTime inJune = LocalDateTime.now().withMonth(Month.JUNE.getValue());
截断
截断会将所有小于指定字段的时间字段重置为零。如图所示,分钟及以下的时间字段将被设置为零
// Old
Calendar now = Calendar.getInstance();
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
Date truncated = now.getTime();
// New
LocalTime truncated = LocalTime.now().truncatedTo(ChronoUnit.HOURS);
时区转换
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
Date centralEastern = calendar.getTime();
// New
ZonedDateTime centralEastern = LocalDateTime.now().atZone(ZoneId.of("CET"));
获取两个时间点之间的跨度
// Old
GregorianCalendar calendar = new GregorianCalendar();
Date now = new Date();
calendar.add(Calendar.HOUR, 1);
Date hourLater = calendar.getTime();
long elapsed = hourLater.getTime() - now.getTime();
// New
LocalDateTime now = LocalDateTime.now();
LocalDateTime hourLater = LocalDateTime.now().plusHours(1);
Duration span = Duration.between(now, hourLater);
时间格式化和解析
DateTimeFormatter
是替换旧的SimpleDateFormat
的线程安全版本,提供了更多功能。
// Old
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date now = new Date();
String formattedDate = dateFormat.format(now);
Date parsedDate = dateFormat.parse(formattedDate);
// New
LocalDate now = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = now.format(formatter);
LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);
一个月中的天数
// Old
Calendar calendar = new GregorianCalendar(1990, Calendar.FEBRUARY, 20);
int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
// New
int daysInMonth = YearMonth.of(1990, 2).lengthOfMonth();
4. 与遗留代码交互
在许多情况下,用户可能需要确保与依赖旧日期库的第三方库的互操作性。
在Java 8中,旧日期库类已经被扩展,提供了将它们转换为新日期API相应对象的方法。新类也提供了类似的功能。
Instant instantFromCalendar = GregorianCalendar.getInstance().toInstant();
ZonedDateTime zonedDateTimeFromCalendar = new GregorianCalendar().toZonedDateTime();
Date dateFromInstant = Date.from(Instant.now());
GregorianCalendar calendarFromZonedDateTime = GregorianCalendar.from(ZonedDateTime.now());
Instant instantFromDate = new Date().toInstant();
ZoneId zoneIdFromTimeZone = TimeZone.getTimeZone("PST").toZoneId();
5. 总结
在这篇文章中,我们探讨了Java 8中可用的新日期时间API。我们比较了它与已弃用的API,并通过多个示例指出了它们的区别。
请注意,我们只是简单地介绍了新日期时间API的强大功能的皮毛。请务必阅读官方文档,了解新API提供的完整工具集。
示例代码可以在GitHub项目中找到。