ImmutableでスレッドセーフになったJavaの新しい日時APIの基礎知識ここが大変だよJava 8 Date-Time API(1)(1/5 ページ)

Date-Time APIの概要や、Java 8より前の旧日時APIから何が改善されたのかに加え、新しく追加されたさまざまなクラスについて解説します。

» 2014年12月17日 18時00分 公開
[長谷川智之,株式会社ビーブレイクシステムズ]

 2014年3月にリリースされたJava 8では、それより前まで日時を扱っていた「java.util.Date」「java.util.Calendar」などとは異なる、新しい日時を扱うAPIが追加されました。この連載ではJava 8より追加されたDate-Time APIについて、一般的な業務システムを構築する際に必要な情報を簡単に見ていきます。

 今回は、Java 8より前の旧日時APIから何が改善されたのか、Date-Time APIからどのようなクラスが用意されているのかなど全体的な概要を紹介。次回以降は、Date-Time APIで何ができるのか、Date-Time APIという日時APIとの相互変換などについて解説していく予定です。

 なお、本連載では、Java 8より追加されたjava.timeパッケージ以下のAPIを「Date-Time API」と呼び、それより前のjava.util.Dateなど、日時のAPIを「旧日時API」と呼びます。また、実行環境のタイムゾーンは日本にしています。

※Java 8の開発環境に関しては、記事「初心者のためのJavaラムダ式入門とJDKのインストール、IDEの環境構築」を参照してください。

旧日時APIからのDate-Time APIの改善点

 Java 8より追加されたDate-Time APIは、それまでの旧日時APIとはまったく違うものとして設計され、旧日時APIが抱えていたいくつもの問題が改善されています。そして、新たにさまざまなケースに対応できるクラスが数多く追加されています。

 Javaの初期からあるjava.util.Dateやjava.util.Calendarなどのクラス、文字列の変換や解析を行っていたjava.text.DateFormatなどの旧日時APIは、時が経つとともに、開発の手法や運用の環境が変わっていったため、さまざまな要求や問題が出てきています。

 例えば、これら旧日時APIは生成後も状態が変わるMutableなクラスであるため、final宣言をしても、そのオブジェクトの値自体は変更可能であることや、ちょっとしたこと(例えばjava.util.Dateでの加算や減算)をするための機能が乏しいことなど、さまざまな点でJavaの開発者の要求に応えられていません。

 その中でも特に大きな問題は、旧日時APIではスレッドセーフではないため、マルチスレッドの環境でそれらのクラスが扱われる場合、そのことを意識していないと実行時に意図した結果にならない危険性を持っていることになります。

 例えば、よく知られている問題として、同じSimpleDateFormatのインスタンスを使って複数スレッドで実行した場合、次のように意図した結果にならないことがあります。

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
 
Calendar calendar1 = Calendar.getInstance();
calendar1.set(1999, Calendar.JANUARY, 2, 0, 0, 0);
Date date1 = calendar1.getTime();
 
Calendar calendar2 = Calendar.getInstance();
calendar2.set(2014, Calendar.DECEMBER, 31, 0, 0, 0);
Date date2 = calendar2.getTime();
 
System.out.println("date1= " + dateFormat.format(date1));
System.out.println("date2= " + dateFormat.format(date2));
Thread thread1 = new Thread(() -> {
  for (int i = 0; i < 100; i++) {
    try {
      String result = dateFormat.format(date1);
      System.out.println("Thread1: " + result);
    } catch (Exception e) {
      e.printStackTrace();
      break;
    }
  }
});
 
Thread thread2 = new Thread(() -> {
  for (int i = 0; i < 100; i++) {
    try {
      String result = dateFormat.format(date2);
      System.out.println("Thread2: " + result);
    } catch (Exception e) {
      e.printStackTrace();
      break;
    }
  }
});
 
System.out.println("start");
thread1.start();
thread2.start();
サンプル
実行結果

 ここではThread1はdate1のフォーマットの結果である「1999/01/02」を、Thread2ではdate2の結果である「2014/12/31」が出ることを期待していますが、ところどころ違うものが出力されていることが確認できます。このように、開発者がスレッドセーフでないことを認識していないと、気付かれにくいバグを埋め込んでしまう危険性があります。

 その他にも問題が細かいレベルであり、例えば、旧日時APIでは月を0から数えていたり、Calendarクラスでは月などのさまざまな項目を表すのに全てintの定数を使っているためタイプセーフではなかったり、時刻のみや日付のみを扱えなかったりと、開発者にとって使いづらいと思われる点が多々あります。

 とはいえ、これらのクラスはJavaの初期からあったクラスであり、C++から発展したJavaとしては、その当時では普通の設計であったことが想像できます。しかし、現在では開発をしている環境も運用されている状況も変わってきているため、当時ではあまり問題として考慮されていなかったようなことも、今日では解決すべき問題として見られるようになってきています。

 そして、これらの問題を解決すべく、Java 8より新たにDate-Time APIが導入されました。このDate-Time APIはそれまでの旧日時APIを拡張したものではなく、全く新しいAPIとして設計されていて、いくつもの新しいクラスが用意され、旧日時APIからの問題が改善されています。

 特に大きな改善点は、Date-Time APIで用意されたクラスは生成後に状態が変わらないImmutableなクラスになっており、スレッドセーフになっている点です。その他にも、Date-Time APIでは月を1から数えたり、年月日だけ扱うクラスや時刻だけを扱うクラスが用意されていたり、タイムゾーンを使った国際化が楽になるクラスが用意されていたりと、さまざまな改善が見られます。

       1|2|3|4|5 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。