1. 概要

このチュートリアルでは、Java17で導入されたInstantSource インターフェイスについて詳しく説明します。このインターフェイスは、が現在のインスタントのプラグイン可能な表現を提供し、タイムゾーンへの参照を回避します。

2. InstantSourceインターフェース

このインターフェースの最初の目標は、元の提案および関連の問題に見られるように、java.timeによって提供されるタイムゾーンへの抽象化を作成することです。時計。 また、インスタントを取得するコードの部分のテスト中にスタブを簡単に作成できます。

次の例に示すように、現在のインスタントに安全にアクセスする方法を提供するために、Java17で追加されました。

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

そして、私たちは簡単に瞬間を得ることができます:

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

その実装は、インスタントを取得するためにどこでも使用できるオブジェクトを作成し、テスト目的でスタブ実装を作成する効果的な方法を提供します。

このインターフェースを使用する利点を詳しく見ていきましょう。

3. 問題と解決策

InstantSource インターフェースをよりよく理解するために、それが対処するために作成された問題と、それが提供する実際の解決策について詳しく見ていきましょう。

3.1. テストの問題

Instant の取得を含むコードのテストは通常悪夢であり、そのInstantを取得する方法が LocalDateTime.now()。などの現在のデータソリューションに基づいている場合はさらにそうです。

テストで特定の日付を提供するために、通常、外部の日付ファクトリを作成したり、テスト内にスタブインスタンスを提供したりするなどの回避策を作成します。

この問題の回避策の例として、次のコードを見てみましょう。

InstantExample クラスは、 InstantWrapper (または回避策)を使用してインスタントを回復します。

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 が行うことです。これは、必要な場所で使用できるInstantsの外部ファクトリを提供します。 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()など、から取得するさまざまな場所があります。またはLocalDateTime.now.toInstant(zoneOffset)。 問題は、選択したフレーバーによっては、タイムゾーンの問題が発生する可能性があることです。

たとえば、Clockクラスでインスタントを要求するとどうなるか見てみましょう。

Clock.systemDefaultZone().instant();

このコードは次の結果を生成します。

2022-01-05T06:47:15.001890204Z

同じ瞬間を別のソースから聞いてみましょう:

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

これにより、次の出力が生成されます。

2022-01-05T07:47:15.001890204Z

同じ瞬間を取得する必要がありましたが、実際には、2つの間に60分の違いがあります。

最悪の部分は、コードの異なる部分でこれら2つのインスタントソースを使用して同じコードで作業している2人以上の開発者がいる可能性があることです。 その場合、問題が発生します。

通常、この時点ではタイムゾーンを処理したくありません。 ただし、インスタントを作成するには、ソースが必要です。そのソースには、常にタイムゾーンが付加されています。

3.4. タイムゾーンの問題の解決策

InstantSourceは、インスタントのソースを選択することから私たちを抽象化します。 その選択はすでに私たちのために行われています。 次のセクションで説明するように、別のプログラマーがシステム全体のカスタム実装を設定したか、Java17で提供されているものを使用している可能性があります。

InstantExample が示すように、 InstantSource が接続されており、他に何も知る必要はありません。 InstantWrapper の回避策を削除し、代わりにプラグインされたInstantSourceを使用することができます。

このインターフェースを使用する利点を確認したので、静的メソッドとインスタンスメソッドを使用して、他に何が提供されるかを見てみましょう。

4. ファクトリメソッド

次のファクトリメソッドを使用して、InstantSourceオブジェクトを作成できます。

  • system()–デフォルトのシステム全体の実装
  • tick(InstantSource、Duration)– は、指定された期間の最も近い表現に切り捨てられた InstantSourceを返します
  • fixed(Instant)– は、が常に同じInstantを生成するInstantSourceを返します。
  • offset(InstantSource、Duration)– は、が指定されたオフセットでインスタントを提供するInstantSourceを返します。

これらのメソッドのいくつかの基本的な使用法を見てみましょう。

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 のインスタンスと対話するために使用できるメソッドはです。

  • instance()– は、InstantSourceによって指定された現在のInstantを返します。
  • millis()– は、InstantSourceによって提供される現在のInstantのミリ秒表現を返します。
  • withZone(ZoneId)–ZoneId を受信し、指定されたZoneIdを持つ指定されたInstantSourceに基づいてクロックを返します。

5.1. インスタント()

このメソッドの最も基本的な使用法は次のとおりです。

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()

特定のZoneIdClockインスタンスを取得しましょう。

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

これにより、次のように出力されます。

SystemClock[-04:00]

6. 結論

この記事では、 InstantSource インターフェースを使用して、対処するために作成された重要な問題を列挙し、日常業務でそれをどのように活用できるかについての実際の例を示しました。

いつものように、コードはGitHubから入手できます。