Ohhnews

分类导航

$ cd ..
Baeldung原文

Java中Date与Instant类型转换指南

#java#日期处理#类型转换#时间戳#软件开发

[LOADING...]

1. 概述

在现代 Java 应用程序中,我们经常需要同时处理传统的 Date API 和 Java 8 java.time 包中较新的 Instant 类。虽然 Java 8 的日期时间 API 在清晰度、不可变性和线程安全性方面有了显著改进,但许多现有的库和框架仍然依赖于 Date

在本教程中,我们将探讨如何在 DateInstant 之间进行转换,讨论它们的精度差异,并提供完整的 JUnit 5 测试来验证转换行为。

2. 理解 DateInstant

Date 类表示时间轴上的特定瞬间,内部存储为自 Unix 纪元(1970 年 1 月 1 日 00:00:00 UTC)以来的毫秒数。然而,它是可变的,属于旧版 API。

Java 8 引入的 Instant 类表示 UTC 时间轴上的一个时刻,精度达到纳秒级。它是不可变且线程安全的,因此成为现代应用程序的首选。

这两个类都基于纪元值(epoch values)表示时间点。它们的主要区别在于 API 设计和精度处理。

3. 将 Date 转换为 Instant

Java 8 引入了一种使用 toInstant() 方法将 Date 转换为 Instant 的直接方式。为了保持代码的简洁和可重用性,我们将转换逻辑集中在一个工具类中:

$ java
public final class DateInstantConverter {
    public static Instant toInstant(Date date) {
        if (date == null) {
            return null;
        }
        return date.toInstant();
    }
    // ...
}

该工具类隔离了转换逻辑,保持了领域代码的整洁,并确保了整个应用程序中空值处理的一致性。

现在,我们来验证将 Date 转换为 Instant 是否保留了纪元毫秒数:

$ java
@Test
void shouldConvertDateToInstant() {
    Date date = new Date(1708752000000L);
    Instant instant = DateInstantConverter.toInstant(date);
    assertNotNull(instant);
    assertEquals(date.getTime(), instant.toEpochMilli());
}

此测试证实了两个对象表示相同的纪元毫秒值。匹配毫秒数确保了转换行为的正确性。

在实际应用中,转换方法可能会接收到空值,因此我们通过以下测试用例来验证转换的空安全性:

$ java
@Test
void shouldReturnNullWhenDateIsNull() {
    Instant instant = DateInstantConverter.toInstant(null);
    assertNull(instant);
}

这避免了在服务层中重复进行不必要的空值检查。

4. 将 Instant 转换为 Date

要将 Instant 转回 Date,我们使用静态方法 Date.from()。在同一个工具类中,我们添加了相应的转换方法:

$ java
public final class DateInstantConverter {
    // ...
    public static Date toDate(Instant instant) { 
        if (instant == null) { 
            return null; 
        } 
        return Date.from(instant); 
    }
}

由于两个类都表示基于纪元的时间点,因此转换是对称的。让我们验证一下转换逻辑:

$ java
@Test
void shouldConvertInstantToDate() {
    Instant instant = Instant.ofEpochMilli(1708752000000L);
    Date date = DateInstantConverter.toDate(instant);
    assertNotNull(date);
    assertEquals(instant.toEpochMilli(), date.getTime());
}

该测试验证了纪元毫秒值在转换过程中保持一致。只要保持毫秒精度,其行为就是可预测的。

与上一节类似,为了检查转换的空安全性,我们添加了此测试用例:

$ java
@Test
void shouldReturnNullWhenInstantIsNull() {
    Date date = DateInstantConverter.toDate(null);
    assertNull(date);
}

同样,这种方法避免了在服务层中不必要的空值检查冗余。

5. DateInstant 之间的精度差异

DateInstant 之间的一个关键区别在于精度。Date 仅以毫秒为单位存储时间。而 Instant 则以秒和纳秒为单位存储时间。当从 Instant 转换为 Date 时,超过毫秒精度的纳秒部分会被截断。

为了验证毫秒精度的保留情况,我们执行一次往返转换(round-trip):

$ java
@Test
void shouldPreserveMillisecondPrecisionInRoundTrip() {
    Instant originalInstant = Instant.now(); 
    Date date = DateInstantConverter.toDate(originalInstant);
    Instant convertedBack = DateInstantConverter.toInstant(date);
    assertEquals(originalInstant.toEpochMilli(), convertedBack.toEpochMilli());
}

此测试确认将 Instant 转换为 Date 再转回 Instant 可以保留毫秒精度。我们特意比较了纪元毫秒数,而不是直接使用对象相等性判断。

我们还可以显式验证截断行为:

$ java
@Test
void shouldTruncateNanosecondsWhenConvertingToDate() {
    Instant instantWithNanos = Instant.ofEpochSecond(1000, 123456789);
    Date date = DateInstantConverter.toDate(instantWithNanos);
    Instant convertedBack = DateInstantConverter.toInstant(date);
    assertEquals(instantWithNanos.toEpochMilli(), convertedBack.toEpochMilli());
}

在此示例中,超过毫秒精度的纳秒部分在转换过程中被移除。这是预期的行为,在处理高精度系统时必须予以考虑。

在现代应用程序中,我们应该在领域模型和业务逻辑中优先使用 Instant

我们仅应在集成边界(例如需要 Date 的旧版 API 或数据库驱动程序)处将其转换为 Date

同时,集中处理转换逻辑并使用适当的单元测试来验证行为也是一种良好的实践。在测试中比较纪元毫秒数可以防止与精度相关的问题。

7. 结论

在本文中,我们了解了在 Java 8 及更高版本中,在 DateInstant 之间进行转换非常简单。要将 Date 转换为 Instant,我们使用 toInstant() 方法;要将 Instant 转换为 Date,我们使用 Date.from() 方法。尽管这两个类都表示时间轴上的同一时刻,但它们的精度模型不同。Date 仅支持毫秒精度,而 Instant 支持纳秒精度。通过使用适当的 JUnit 测试来验证这些转换,我们可以确保正确性,并防止生产系统中出现微妙的精度相关问题。

一如既往,本文中展示的代码可在 GitHub 上找到。