新しいJava 8 Date Time APIへの移行
1. 概要
このチュートリアルでは、Java 8で導入された新しいDate Time APIを活用するために、コードをリファクタリングする方法を学びます。
2. 新しいAPIの概要
以前は、Javaで日付を扱うのは困難でした。 JDKが提供する古い日付ライブラリには、java.util.Date, java.util.Calendarとjava.util.Timezoneの3つのクラスしか含まれていませんでした。
これらは、最も基本的なタスクにのみ適していました。 リモートで複雑なものでも、開発者はサードパーティのライブラリを使用するか、大量のカスタムコードを記述する必要がありました。
Java 8 introduced a completely new Date Time API(java.util.time.*)は、JodaTimeと呼ばれる人気のあるJavaライブラリに大まかに基づいています。 この新しいAPIは、日付と時刻の処理を劇的に簡素化し、古い日付ライブラリの多くの欠点を修正しました。
1.1. APIの明確さ
新しいAPIの最初の利点はclarityです。APIは非常に明確で、簡潔で、理解しやすいものです。 フィールド番号など、古いライブラリに見られる多くの矛盾はありません(カレンダーでは、月はゼロから始まりますが、曜日は1から始まります)。
1.2. APIの柔軟性
もう1つの利点は、柔軟性です–working with multiple representations of time。 古い日付ライブラリには、単一の時間表現クラス–java.util.Dateのみが含まれていました。これは、その名前にもかかわらず、実際にはタイムスタンプです。 Unixエポックから経過したミリ秒数のみを保存します。
新しいAPIにはさまざまな時間表現があり、それぞれが異なるユースケースに適しています。
-
Instant –ある時点(タイムスタンプ)を表します
-
LocalDate –日付(年、月、日)を表します
-
LocalDateTime –LocalDateと同じですが、ナノ秒の精度の時間が含まれます
-
OffsetDateTime –LocalDateTimeと同じですが、タイムゾーンオフセットがあります
-
LocalTime –ナノ秒の精度で、日付情報のない時間
-
ZonedDateTime –OffsetDateTimeと同じですが、タイムゾーンIDが含まれています
-
OffsetLocalTime –LocalTimeと同じですが、タイムゾーンオフセットがあります
-
MonthDay –月と日、年または時間なし
-
YearMonth –月と年、日と時間なし
-
Duration –秒、分、時間で表される時間。 ナノ秒精度
-
Period –日、月、年で表される時間
1.3. 不変性とスレッドセーフ
もう1つの利点は、Java 8 Date Time APIのすべての時間表現がimmutable and thus thread-safe.であることです。
すべての変更メソッドは、元のオブジェクトの状態を変更するのではなく、新しいコピーを返します。
java.util.Dateなどの古いクラスはスレッドセーフではなく、非常に微妙な同時実行バグを引き起こす可能性がありました。
1.4. メソッドの連鎖
すべての変更メソッドをチェーン化して、1行のコードで複雑な変換を実装できます。
ZonedDateTime nextFriday = LocalDateTime.now()
.plusHours(1)
.with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
.atZone(ZoneId.of("PST"));
2. 例
以下の例は、古いAPIと新しい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では、古い日付ライブラリクラスは、新しい日付APIから対応するオブジェクトに変換するメソッドで拡張されています。 新しいクラスは同様の機能を提供します。
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. 結論
この記事では、Java 8で使用可能な新しいDate Time APIについて説明しました。 非推奨のAPIと比較した場合の利点を確認し、複数の例を使用して違いを指摘しました。
新しいDate Time APIの機能の表面をかろうじてスクラッチしたことに注意してください。 公式のドキュメントを読んで、新しいAPIが提供するすべてのツールを見つけてください。
コード例はGitHub projectにあります。