Java SE 8のDate and Time API、ラムダ式、Stream APIは本当に使えるか? 従来コードとのパフォーマンス面の違いを検証する

Oracle Java & Developers編集部
2016-08-10 15:00:00
  • このエントリーをはてなブックマークに追加

無駄なコーディングを省き、本質的な処理を書きやすくするラムダ式

 ラムダ式の導入は、Javaの歴史における最大の仕様変更である。これは一言で言えば、関数型インタフェースを簡単に実装するための表記法だ。Javaにおける関数型インタフェースの特徴の1つは、とは、メソッドの宣言を1つだけ持つ点である。

 Javaにおけるインタフェースは、implementsしたうえでクラスを実装するか、匿名クラスで実装して使うのが一般的である。しかし、これらの方法では“本来の関心事”、すなわち実装したい処理の本質ではないコードまで記述しなければならないことが多く、プログラムが冗長になりがちだ。ここで言う本来の関心事とは、メソッドに渡す“引数”と“処理”、そして処理を行って返す“結果”にほかならない。そこで、関数型インタフェースに限り、極めてシンプルに本質部分を記述できるようにしたものがラムダ式というわけだ。

ラムダ式なら関心事だけを記述すればよい ラムダ式なら関心事だけを記述すればよい
※クリックすると拡大画像が見られます

 伊藤氏は、指定した数が素数であるかどうかを確認するプログラムを作り、条件式の部分をラムダ式で記述して、同式を使わない場合とのコンパイル数、コンパイル時間、処理時間の違いを比較した。比較に用いたテスト・コードは次のようなものだ。

ラムダ式のテスト・コード ラムダ式のテスト・コード
※クリックすると拡大画像が見られます

 検証の結果は下図のようになった。

リソース使用量 リソース使用量
※クリックすると拡大画像が見られます
コンパイル数 コンパイル数
※クリックすると拡大画像が見られます
コンパイル時間 コンパイル時間
※クリックすると拡大画像が見られます
処理時間 処理時間
※クリックすると拡大画像が見られます

 これらの結果からわかるように、ラムダ式ではコンパイルされるメソッドの数が大幅に増加する。それに伴ってコンパイル時間も増えるが、これはミリ秒単位のオーダーであり、気にするほどの差ではないという。そして、肝心の処理時間はわずかにラムダ式のほうが短くなっている。

簡潔な並列処理の実装を可能にするStream API

 ラムダ式は単体でも強力な新機能だが、Stream APIとの親和性が高く、両者を組み合わせることでより大きな効果が得られる。

 Stream APIは、繰り返し行われるような処理を簡潔に実装するためのAPIである。繰り返し処理の内部で行われる操作は、多くの場合、「中間操作」と「終端操作」の2種類に分類できる。中間操作とはデータのフィルタリングや変換など繰り返しの途中で行う操作を指し、終端操作とは処理が終わったデータを出力したり、コレクションに格納したりといった繰り返しの最後に行う操作を指す。

 従来、このような繰り返し処理はfor文や拡張for文、while文を使って実装するのが一般的だった。次に示すコードは、30歳以上の社員の名前をリストに格納する処理を拡張for文を使って実装した例だ。拡張for文やインタフェースIteratorを使用したイテレータのことを「外部イテレータ」と呼ぶ。

for文を使った繰り返し処理の実装例 for文を使った繰り返し処理の実装例
※クリックすると拡大画像が見られます

 こうした外部イテレータによる実装については、これまで次のような問題が指摘されてきた。

  • 同じようなコードが何回も現れるため、メンテナンス性が悪い
  • 1回当たりの処理の粒度が小さいため、パフォーマンスの向上が難しい
  • 並列処理に対応するための開発コストが高い

 これに対して、繰り返し処理を内部に隠蔽して定義する方式を「内部イテレータ」と呼ぶ。Stream APIは、繰り返し処理を内部イテレータを使って実装するためのAPIであり、次のような特徴を備える。

  • 内部イテレータによる繰り返し処理が可能
  • 中間操作と終端操作の組み合わせで処理を定義する
  • Fork Join Frameworkによってパラレル化を実現する
  • オブジェクトだけではなく、プリミティブ型も扱える

 先ほどfor文で書いた繰り返し処理をStream APIを使って書き直すと、次のようになる。

Stream APIを使った繰り返し処理の実装例 Stream APIを使った繰り返し処理の実装例
※クリックすると拡大画像が見られます