原创

Java17中的InstantSource简介

温馨提示:
本文最后更新于 2022年11月18日,已超过 18 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

1.概述

在本教程中,我们将深入了解Java 17中引入的  InstantSource 接口, 提供当前瞬间的可插拔表示, 并且避免引用时区。

2 InstantSource 接口

这个接口的第一个目标,正如我们在原文中看到的 提议 和一个相关问题,是创建对时区的抽象 java.time.Clock,它还简化了在测试检索实例的代码部分期间创建存根的过程。

它是在Java 17中添加的 为了提供访问当前时刻的安全方式, 正如我们在以下示例中看到的:

class AQuickTest {
    InstantSource source;
    ...
    Instant getInstant() {
        return source.instant();
    }
}

然后,我们可以简单地得到一个瞬间:

var quickTest = new AQuickTest(InstantSource.system());
quickTest.getInstant();

它的实现创建了可以在任何地方用于检索实例的对象,并且它提供了一种为测试目的创建存根实现的有效方法。

让我们更深入地了解一下使用此界面的好处。

3.问题与解决方案

为了更好地理解 InstantSource 接口,让我们深入探讨它创建来解决的问题以及它提供的实际解决方案。

3.1.测试问题

测试涉及检索 Instant 通常是一场噩梦,而当获取即时消息的方法基于当前的数据解决方案时,更是如此,例如 LocalDateTime.now()。

为了让测试提供特定的日期,我们通常会创建变通方法,例如创建外部日期工厂,并在测试中提供存根实例。

让我们看看下面的代码,作为解决此问题的解决方法的示例。

这个 InstantExample类使用 InstantWrapper(或解决方法)去恢复Instant:

class InstantExample {
    InstantWrapper instantWrapper;
    Instant getCurrentInstantFromInstantWrapper() {
        return instantWrapper.instant();
    }
}

还有我们的 InstantWrapper解决方法类本身如下所示:

class InstantWrapper {
    Clock clock;
    InstantWrapper() {
        this.clock = Clock.systemDefaultZone();
    }
    InstantWrapper(ZonedDateTime zonedDateTime) {
        this.clock = Clock.fixed(zonedDateTime.toInstant(), zonedDateTime.getZone());
    }
    Instant instant() {
        return clock.instant();
    }
}

然后,我们可以使用它为测试提供一个固定的瞬间:

// given
LocalDateTime now = LocalDateTime.now();
InstantExample tested = new InstantExample(InstantWrapper.of(now), null);
Instant currentInstant = now.toInstant(ZoneOffset.UTC);
// when
Instant returnedInstant = tested.getCurrentInstantFromWrapper();
// then
assertEquals(currentInstant, returnedInstant);

3.2.测试问题的解决方案

本质上,我们上面应用的解决方法是 InstantSource , 它提供了一个Instant外部工厂,我们可以在任何需要的地方使用Java 17提供了默认的系统范围实现(在 Clock 类里),我们还可以提供自己的:

class InstantExample {
    InstantSource instantSource;
    Instant getCurrentInstantFromInstantSource() {
        return instantSource.instant();
    }
}

这个 InstantSource 是可插拔的。也就是说,它可以使用依赖注入框架注入,或者作为构造函数参数传递到我们正在测试的对象中。因此,我们可以很容易地创建一个 InstantSource,将其提供给被测试对象,并使其返回测试所需的瞬间:

// given
LocalDateTime now = LocalDateTime.now();
InstantSource instantSource = InstantSource.fixed(now.toInstant(ZoneOffset.UTC));
InstantExample tested = new InstantExample(null, instantSource);
Instant currentInstant = instantSource.instant();
// when
Instant returnedInstant = tested.getCurrentInstantFromInstantSource();
// then
assertEquals(currentInstant, returnedInstant);

3.3.时区问题

当我们需要一个Instant,我们可能在不同的地方获取它,像Instant.now(), Clock.systemDefaultZone().instant() or even LocalDateTime.now.toInstant(zoneOffset).问题是,根据我们选择的口味,它可能会引入时区问题。

例如,让我们看看在 Class 类:

Clock.systemDefaultZone().instant();

此代码将产生以下结果:

2022-01-05T06:47:15.001890204Z

让我们从不同的来源询问相同的瞬间:

LocalDateTime.now().toInstant(ZoneOffset.UTC);

这将产生以下输出:

2022-01-05T07:47:15.001890204Z

我们应该得到同样的时刻,但事实上,两者之间有60分钟的差距。

最糟糕的是,可能有两个或更多的开发人员在代码的不同部分使用这两个即时源处理同一代码。如果是这样的话,我们就有问题了。

此时,我们通常不想处理时区问题。但是,要创建即时,我们需要一个源,并且该源总是附带一个时区。

3.4.时区问题的解决方案

InstantSource 使我们无法选择瞬间的来源。这个选择已经为我们做了。可能是另一个程序员设置了系统范围的自定义实现,或者我们正在使用Java17提供的实现,我们将在下一节中看到。

正如 即时示例节目,我们得到了 即时来源 插上电源,我们不需要知道任何其他信息。我们可以删除 即时包装器 解决方法,只需使用插入的即时来源相反

现在,我们已经看到了使用此接口的好处,让我们通过查看它的静态和实例方法,看看它还能提供什么。

4.工厂方法

以下工厂方法可用于创建InstantSource对象:

  • system()-默认系统范围实现
  • tick(InstantSource, Duration)-返回一个 即时来源 截断为给定持续时间的最接近表示
  • fixed(Instant)-返回一个 即时来源 那个 总是产生相同的结果 Instant
  • offset(InstantSource, Duration)-返回一个 即时来源那个 提供具有给定偏移量的Instant

让我们来看看这些方法的一些基本用法。

4.1. system()

Java 17中当前的默认实现是 Clock.SystemInstantSource 类

Instant i = InstantSource.system().instant();

4.2. tick()

基于前面的示例:

Instant i = InstantSource.system().instant();
System.out.println(i);

运行此代码后,我们将获得以下输出:

2022-01-05T07:44:44.861040341Z

但是,如果我们采用2小时的刻度持续时间:

Instant i = InstantSource.tick(InstantSource.system(), Duration.ofHours(2)).instant();

然后,我们将得到以下结果:

2022-01-05T06:00:00Z

4.3. fixed()

当我们需要创建存根 InstantSource 出于测试目的:

LocalDateTime fixed = LocalDateTime.of(2022, 1, 1, 0, 0);
Instant i = InstantSource.fixed(fixed.toInstant(ZoneOffset.UTC)).instant();
System.out.println(i);

上述始终返回相同的瞬间:

2022-01-01T00:00:00Z

4.4. offset()

根据前面的示例,我们将对固定 InstantSource 查看它返回的内容:

LocalDateTime fixed = LocalDateTime.of(2022, 1, 1, 0, 0);
InstantSource fixedSource = InstantSource.fixed(fixed.toInstant(ZoneOffset.UTC));
Instant i = InstantSource.offset(fixedSource, Duration.ofDays(5)).instant();
System.out.println(i);

执行此代码后,我们将获得以下输出:

2022-01-06T00:00:00Z

5.实例方法

可用于与实例交互的方法 InstantSource:

  • instant()-返回当前值 瞬间即时来源
  • millis()-返回电流的毫秒表示 瞬间 由提供 即时来源
  • withZone(ZoneId)-接收 ZoneID 并返回 基于给定的时钟 InstantSource 具有指定的 ZoneID

5.1. instant()

此方法的最基本用法是:

Instant i = InstantSource.system().instant();
System.out.println(i);

运行此代码将显示以下输出:

2022-01-05T08:29:17.641839778Z

5.2. millis()

为了从 InstantSource:

long m = InstantSource.system().millis();
System.out.println(m);

运行后,我们将得到以下结果:

1641371476655

5.3. withZone()

让我们来个 Clock 特定的实例 ZoneID:

Clock c = InstantSource.system().withZone(ZoneId.of("-4"));
System.out.println(c);

这将简单地打印以下内容:

SystemClock[-04:00]

6.结论

在本文中,我们已经了解了 InstantSource 接口,列举了创建它来解决的重要问题,并展示了我们如何在日常工作中利用它的真实例子。

与往常一样,代码可用 在GitHub上.

正文到此结束