1. Overview
1.概述
In this tutorial, we’ll dive into the InstantSource interface introduced in Java 17, which provides a pluggable representation of the current instant and avoids references to time zones.
在本教程中,我们将深入研究Java 17中引入的InstantSource接口,它提供了一个可插拔的当前瞬时表示法,并避免了对时区的引用。
2. The InstantSource Interface
2.InstantSource接口
The first goal of this interface, as we can see in the original proposal and a related issue, is to create an abstraction to the time zone provided by java.time.Clock. It also eases the creation of stubs during testing for portions of code that retrieve instants.
正如我们在最初的提案和相关问题中所看到的,这个接口的第一个目标是创建一个对java.time.Clock所提供的时区的抽象。这也便于在测试期间为检索实例的部分代码创建存根。
It was added in Java 17 to provide a safe way to access the current instant, as we can see in the following example:
它是在Java 17中添加的,提供了一种安全的方式来访问当前的瞬时,我们可以在下面的例子中看到。
class AQuickTest {
InstantSource source;
...
Instant getInstant() {
return source.instant();
}
}
And then, we can simply get an instant:
然后,我们可以简单地得到一个瞬间。
var quickTest = new AQuickTest(InstantSource.system());
quickTest.getInstant();
Its implementations create objects which can be used anywhere to retrieve instants, and it provides an effective way to create stub implementations for testing purposes.
它的实现创建的对象可以在任何地方用于检索实例,它提供了一种有效的方式来创建测试用的存根实现。
Let’s take a deeper look at the benefits of using this interface.
让我们更深入地了解一下使用这种界面的好处。
3. Problems & Solutions
3.问题与解决方案
In order to better understand the InstantSource interface, let’s dive into the problems it was created to address and the actual solutions it provides.
为了更好地理解InstantSource接口,让我们深入了解创建它所要解决的问题以及它提供的实际解决方案。
3.1. The Testing Issue
3.1.测试问题
Testing code involving the retrieval of an Instant is usually a nightmare, and more so when the way to get that Instant is based on current data solutions, such as LocalDateTime.now().
测试涉及检索Instant的代码通常是一场噩梦,而当获得该Instant的方式是基于当前的数据解决方案,如LocalDateTime.now().时更是如此。
In order for a test to provide a specific date, we usually create workarounds like creating an external date factory and providing a stubbed instance within the test.
为了让测试提供一个特定的日期,我们通常会创建一些变通方法,比如创建一个外部的日期工厂,并在测试中提供一个存根实例。
Let’s look at the following code as an example of a workaround for this issue.
让我们看一下下面的代码,作为解决这个问题的一个例子。
The InstantExample class uses an InstantWrapper (or workaround) to recover an instant:
InstantExample类使用InstantWrapper(或变通方法)来恢复一个瞬间。
class InstantExample {
InstantWrapper instantWrapper;
Instant getCurrentInstantFromInstantWrapper() {
return instantWrapper.instant();
}
}
And our InstantWrapper workaround class itself looks like this:
而我们的InstantWrapper Workaround类本身看起来是这样的。
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();
}
}
Then, we can use it to provide a fixed instant for testing:
然后,我们可以用它来为测试提供一个固定的瞬间。
// 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. Solution to the Testing Issue
3.2.测试问题的解决方案
In essence, the workaround we applied above is what InstantSource does. It provides an external factory of Instants that we can use wherever we need. Java 17 provides a default system-wide implementation (within the Clock class), and we can also provide our own:
实质上,我们上面应用的变通方法就是InstantSource的作用。它提供了一个Instants的外部工厂,我们可以在需要的地方使用。Java 17提供了一个默认的系统范围内的实现(在Clock类中),我们也可以提供我们自己的。
class InstantExample {
InstantSource instantSource;
Instant getCurrentInstantFromInstantSource() {
return instantSource.instant();
}
}
The InstantSource is pluggable. That is, it can be injected using a dependency injection framework, or just passed as a constructor argument, into the object we’re testing. Thus, we can easily create a stubbed InstantSource, provide it to the tested object, and have it return the instant we want for the test:
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. The Time Zone Issue
3.3.时区问题
When we require an Instant, we have many different places to get it from, like Instant.now(), Clock.systemDefaultZone().instant() or even LocalDateTime.now.toInstant(zoneOffset). The problem is that depending on the flavor we choose, it could introduce time zone issues.
当我们需要一个Instant时,我们有很多不同的地方可以获得它,比如Instant.now()、Clock.systemDefaultZone().instant()甚至LocalDateTime.now.toInstant(zoneOffset) 。问题是,根据我们选择的风味,它可能会引入时区问题。
For instance, let’s see what happens when we ask for an instant on the Clock class:
例如,让我们看看当我们在Clock类上请求一个瞬间时会发生什么。
Clock.systemDefaultZone().instant();
This code will produce the following result:
这段代码将产生以下结果。
2022-01-05T06:47:15.001890204Z
Let’s ask the same instant but from a different source:
让我们问同样的问题,但从不同的来源。
LocalDateTime.now().toInstant(ZoneOffset.UTC);
This produces the following output:
这将产生以下输出。
2022-01-05T07:47:15.001890204Z
We should have gotten the same instant, but in fact, there’s a 60-minute difference between the two.
我们本应得到同样的瞬间,但事实上,两者之间有60分钟的差异。
The worst part is that there might be two or more developers working on the same code using these two instant sources at different parts of the code. If that’s the case, we have a problem.
最糟糕的是,可能有两个或更多的开发人员在同一代码上工作,在代码的不同部分使用这两个即时源。如果是这种情况,我们就有问题了。
We do not usually want to deal with time zones at this point. But, to create the instant, we need a source, and that source always comes with a time zone attached.
在这一点上,我们通常不想处理时区问题。但是,为了创造即时性,我们需要一个来源,而这个来源总是附带着一个时区。
3.4. Solution to the Time Zone Issue
3.4.时区问题的解决方案
InstantSource abstracts us from selecting the source of the instants. That selection is already made for us. It could be that another programmer has set up a system-wide custom implementation or that we’re using the one provided by Java 17, as we will see in the next section.
InstantSource将我们从选择实例的来源中抽象出来。这个选择已经为我们做出了。可能是另一个程序员设置了一个系统范围内的自定义实现,或者我们使用的是Java 17提供的实现,我们将在下一节看到。
Just as the InstantExample shows, we got an InstantSource plugged in, and we don’t need to know anything else. We can remove our InstantWrapper workaround and just use the plugged-in InstantSource instead.
正如InstantExample所示,我们得到了一个InstantSource的插件,我们不需要知道其他的东西。我们可以移除我们的InstantWrapper工作法,而只是使用插入的InstantSource来代替。
Now that we’ve seen the benefits of using this interface let’s take a look at what else it has to offer by going through its static and instance methods.
现在我们已经看到了使用这个接口的好处,让我们通过它的静态和实例方法来看看它还能提供什么。
4. Factory Methods
4.工厂方法
The following factory methods can be used to create an InstantSource object:
以下工厂方法可用于创建 InstantSource 对象。
- system() – default system-wide implementation
- tick(InstantSource, Duration) – returns an InstantSource truncated to the nearest representation of the given duration
- fixed(Instant) – returns an InstantSource that always produces the same Instant
- offset(InstantSource, Duration) – returns an InstantSource that provides Instants with the given offset
Let’s see some basic usages of these methods.
让我们看看这些方法的一些基本用法。
4.1. system()
4.1. system()
The current default implementation in Java 17 is the Clock.SystemInstantSource class.
目前Java 17中的默认实现是Clock.SystemInstantSource类。
Instant i = InstantSource.system().instant();
4.2. tick()
4.2 tick()
Based on the previous example:
根据前面的例子。
Instant i = InstantSource.system().instant();
System.out.println(i);
After running this code we’ll get the following output:
运行这段代码后,我们将得到以下输出。
2022-01-05T07:44:44.861040341Z
But, if we apply a tick duration of 2 hours:
但是,如果我们采用2小时的勾股期限。
Instant i = InstantSource.tick(InstantSource.system(), Duration.ofHours(2)).instant();
Then, we’ll get the result below:
然后,我们会得到下面的结果。
2022-01-05T06:00:00Z
4.3. fixed()
4.3.fixed()
This method is handy when we need to create a stubbed InstantSource for testing purposes:
当我们需要创建一个存根的InstantSource用于测试时,这个方法很方便。
LocalDateTime fixed = LocalDateTime.of(2022, 1, 1, 0, 0);
Instant i = InstantSource.fixed(fixed.toInstant(ZoneOffset.UTC)).instant();
System.out.println(i);
The above always returns the same instant:
上述方法总是返回相同的瞬间。
2022-01-01T00:00:00Z
4.4. offset()
4.4. offset()
Based on the previous example, we’ll apply an offset to the fixed InstantSource to see what it returns:
根据前面的例子,我们将对固定的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);
After executing this code, we will get the following output:
执行这段代码后,我们将得到以下输出。
2022-01-06T00:00:00Z
5. Instance Methods
5.实例方法
The methods available to interact with an instance of InstantSource are:
与InstantSource实例交互的可用方法是::。
- instant() – returns the current Instant given by the InstantSource
- millis() – returns the millisecond representation of the current Instant provided by the InstantSource
- withZone(ZoneId) – receives a ZoneId and returns a clock based on the given InstantSource with the specified ZoneId
5.1. instant()
5.1. instant()
The most basic usage for this method is:
这种方法的最基本用法是。
Instant i = InstantSource.system().instant();
System.out.println(i);
Running this code will show us the following output:
运行这段代码将向我们展示以下输出。
2022-01-05T08:29:17.641839778Z
5.2. millis()
5.2. millis()
In order to get the epoch from an InstantSource:
为了从一个InstantSource中获取历时。
long m = InstantSource.system().millis();
System.out.println(m);
And, after running it we’ll get the following:
而且,运行后我们会得到以下结果。
1641371476655
5.3. withZone()
5.3.withZone()
Let’s get a Clock instance for a specific ZoneId:
让我们为一个特定的ZoneId获得一个Clock实例。
Clock c = InstantSource.system().withZone(ZoneId.of("-4"));
System.out.println(c);
This will simply print the following:
这将简单地打印以下内容。
SystemClock[-04:00]
6. Conclusion
6.结语
In this article, we’ve gone through the InstantSource interface, enumerating the significant issues it was created to address and showing real-life examples of how we could take advantage of it in our daily work.
在这篇文章中,我们浏览了InstantSource接口,列举了它为解决的重大问题,并展示了我们在日常工作中如何利用它的真实案例。
As usual, the code is available over on GitHub.
像往常一样,代码可在GitHub上获得。