1. 概要

このチュートリアルでは、Java8で導入された新しいDateTimeAPIを活用するためにコードをリファクタリングする方法を学習します。

2. 新しいAPIの概要

Javaで日付を操作するのは以前は大変でした。 JDKが提供する古い日付ライブラリには、 java.util.Date、java.util.Calendar java.util.Timezoneの3つのクラスしか含まれていませんでした。

これらは、最も基本的なタスクにのみ適していました。 リモートで複雑なものでも、開発者はサードパーティのライブラリを使用するか、大量のカスタムコードを作成する必要がありました。

Java 8は、JodaTimeと呼ばれる人気のあるJavaライブラリに大まかに基づいた完全に新しいDate Time API java.util.time。*)を導入しました。 この新しいAPIは、日付と時刻の処理を劇的に簡素化し、古い日付ライブラリの多くの欠点を修正しました。

1.1. APIの明確さ

新しいAPIの最初の利点はclarityです。APIは非常に明確で、簡潔で、理解しやすいものです。 フィールド番号など、古いライブラリに見られる多くの矛盾はありません(暦月ではゼロベースですが、曜日は1ベースです)。

1.2. APIの柔軟性

もう1つの利点は、柔軟性です–時間の複数の表現を処理する。 古い日付ライブラリには、単一の時間表現クラス– java.util.Date のみが含まれていました。これは、その名前にもかかわらず、実際にはタイムスタンプです。 Unixエポックから経過したミリ秒数のみを保存します。

新しいAPIにはさまざまな時間表現があり、それぞれがさまざまなユースケースに適しています。

  • Instant –ある時点(タイムスタンプ)を表します
  • LocalDate –日付(年、月、日)を表します
  • LocalDateTime LocalDate と同じですが、ナノ秒の精度の時間が含まれます
  • offsetDateTime LocalDateTime と同じですが、タイムゾーンオフセットがあります
  • LocalTime –ナノ秒の精度で、日付情報のない時刻
  • ZonedDateTime OffsetDateTime と同じですが、タイムゾーンIDが含まれています
  • ResetLocalTime LocalTime と同じですが、タイムゾーンオフセットがあります
  • MonthDay –月と日、年または時間なし
  • YearMonth –月と年、日と時間なし
  • 期間–秒、分、時間で表される時間。 ナノ秒の精度
  • 期間–日、月、年で表される時間

1.3. 不変性とスレッドセーフ

もう1つの利点は、Java 8 Date Time APIのすべての時間表現が不変であり、したがってスレッドセーフであるということです。

すべての変異メソッドは、元のオブジェクトの状態を変更する代わりに、新しいコピーを返します。

java.util.Date などの古いクラスはスレッドセーフではなく、非常に微妙な同時実行性のバグを引き起こす可能性があります。

1.4. メソッドチェーン

すべての変異メソッドをチェーン化できるため、1行のコードで複雑な変換を実装できます。

ZonedDateTime nextFriday = LocalDateTime.now()
  .plusHours(1)
  .with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
  .atZone(ZoneId.of("PST"));

2. 例

以下の例は、新旧両方の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"));

2つの時点の間のタイムスパンを取得する

// 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);

1か月の日数

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

3. レガシーコードとの相互作用

多くの場合、ユーザーは古い日付ライブラリに依存するサードパーティライブラリとの相互運用性を確保する必要があります。

Java 8では、古い日付ライブラリクラスが、新しいDateAPIから対応するオブジェクトに変換するメソッドで拡張されています。 新しいクラスは同様の機能を提供します。

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

4. 結論

この記事では、Java8で利用可能な新しいDateTimeAPIについて説明しました。 非推奨のAPIと比較した場合の利点を確認し、複数の例を使用して違いを指摘しました。

新しいDateTimeAPIの機能のほんの一部にすぎないことに注意してください。 公式ドキュメントを読んで、新しいAPIが提供するすべてのツールを見つけてください。

コード例は、GitHubプロジェクトにあります。