ラムダ式とストリームAPIでJavaプログラミングはここまでシンプルになる!--Java SE 8に今すぐ移行すべき理由

WebLogic Channel編集部
2015-06-17 13:50:00
  • このエントリーをはてなブックマークに追加

Java SE 8ではメソッドの引数に「処理」を渡せる。匿名内部クラスの実装を大幅に簡易化

 ここまで読み進めてきた方の中には、「Java SE 8で並列処理を簡単に実装できることはわかったけど、新しい機能の学習で苦労するのは嫌だな」と思った方がいらっしゃるかもしれません。

 もちろん、これまでのJava SEからJava SE 8に移行するためには、ラムダ式など新しい言語仕様について学ぶ必要があります。しかし、上の例からも、その苦労以上のメリットがありそうだということはわかりますよね。皆さん、安心して、早くJava SE 8の新しいルールを学んでください。

 それでは、Java SE 8によるプログラミングは、これまでとどう変わるのでしょうか。それについて簡単にご説明しましょう。

 これまでのJavaプログラミングでは、まずクラスを書き、その中にメソッドを定義するというスタイルが一般的でした。メソッドの引数に渡すのは「データ」です。

 それに対して、Java SE 8では、メソッドの引数として直接「処理」を渡すことが可能になりました。これがJava SE 8における大きな変化です。

 ここで、「あれ? これまでもメソッドに処理を渡すことができたはずだけど…」と気づいた方がいらっしゃるでしょう。そのとおりです。これまでは匿名内部クラスを使ってメソッドに処理を渡すことができました。

図9:匿名内部クラスを使ったプログラミングの例 図9:匿名内部クラスを使ったプログラミングの例
※クリックすると拡大画像が見られます

 ただし、この方法を使う場合は、本質的な処理を実現するために、そのお膳立てとなるコード(図中のブルーで網掛けしたコード)を沢山書かなくてはなりません。もっとシンプルに実装したいと思っても、Javaの言語仕様上、それができなかったのです。

 そこで、Java SE 8で取り入れられたのがラムダ式だというわけです。ラムダ式は、一言で説明すれば「匿名内部クラスの実装を、これまでよりも大幅に簡略化して書くための記法」だと言えます。

 それでは、具体的にどのくらい完結になるのかを見てみます。図9の匿名内部クラスを使った実装をラムダ式で置き換えてみましょう。それには、図9のコードからメソッド引数をすべて削除し、ボディの前に「->」を付け、その後に渡したい処理を記述します。

 たったこれだけです。なお、実際にはもう少し細かいルールがあり、「単一の抽象メソッド(abstractメソッド)を持つインタフェースを匿名内部クラスとして実装する場合」に限り、ラムダ式を使うことができます。

 Java SE 8では、ラムダ式を適用することのできる43個の関数型インタフェースが新たに追加されました。それらをいきなりすべて覚える必要はありません。初めは、次に示す4つの関数型インタフェースから覚えるとよいでしょう。これらの関数型インタフェースを引数に取る部分でラムダ式を書くことができます。

ラムダ式とストリームAPIで、コードはどこまでシンプルに書けるのか?

 ラムダ式と併せて導入されたストリームAPIは、データのコレクションに対してパイプライン処理を行うための機能を提供しています。その処理の記述にラムダ式を使うことで、可読性が高くシンプルなコードを書けるようになります。

 ストリームAPIには、さまざまな関数やメソッドが用意されています。これもいきなりすべてを覚える必要はありませんが、ストリームAPIによる処理の実装では、「Streamインスタンスの生成」、「中間操作」、「終端操作」という3つのプロセスがあることは覚えておいてください。このプロセスの特徴は、「『中間操作』に当たる操作を何度実行しても、『終端操作』が行われるまで、その操作は未実行の状態にある」ということです。

図12:ストリームAPIによる実装の流れ 図12:ストリームAPIによる実装の流れ
※クリックすると拡大画像が見られます

 それでは、ストリームAPIを使い、あるデータ群の中から先頭が「a」で始まる文字列の抽出と出力を行うプログラムを考えてみましょう。

 まず初めに「Streamインスタンスの生成」を行います。インスタンスは「Stream.of」で直接生成することもできますし、既存のリストから生成することも可能です。

図13:Streamインスタンスの生成 図13:Streamインスタンスの生成
※クリックすると拡大画像が見られます

 次に、ストリームAPIによる「中間操作」を作ります。従来、if文などで書いていた条件抽出の処理はメソッドfilterで、for文を使っていた出力処理はforEachで置き換えて、次のようなかたちで記述します。

図14:ストリームAPIによる操作(中間操作) 図14:ストリームAPIによる操作(中間操作)
※クリックすると拡大画像が見られます

 ここで、引数の青字部分にご注目ください。「Predicate」、「Consumer」とありますが、これらが先ほど「最初に覚えてほしい」とお話しした関数型インタフェースです。この部分にラムダ式を書けるわけですね。Predicateはオブジェクトを受け取り、戻り値としてboolean型を返すインタフェース、Consumerはvoid型を返すインタフェースです。

 それでは、「a」で始まる文字列の抽出と出力を行う処理を、まずJava SE 7までの方法で匿名内部クラスとして実装してみます。その場合は次のようなコードになります。

図15:匿名内部クラスによる文字列の抽出処理 図15:匿名内部クラスによる文字列の抽出処理
※クリックすると拡大画像が見られます

 次に、これをラムダ式で書いてみましょう。先ほどのように、メソッドの引数より前をすべて削除し、ボディの間に「->」を入れるとラムダ式になります。

図16:図15の処理をラムダ式で実装した例 図16:図15の処理をラムダ式で実装した例
※クリックすると拡大画像が見られます

 これで終わりではありません。ここから、さらにプログラムをシンプル化していきます。

 まず、Javaには「型推論」の機能があり、型情報を厳密に指定しなくても、コンパイラが自動的に型を推論して適切なクラスを生成します(もし推論できなかった場合には、警告を出力します)。この型推論により、図16で「String」と指定している部分は削除できます。

図17:型推論を使って図16のコードをシンプル化 図17:型推論を使って図16のコードをシンプル化
※クリックすると拡大画像が見られます

 次に、ラムダ式では左辺に対して1つの値しか返さない場合、引数の括弧も省略できます。

図18:図17のコードから右辺の引数の括弧を省略 図18:図17のコードから右辺の引数の括弧を省略
※クリックすると拡大画像が見られます

 また、処理部分が単一行となる場合は、ブロック({})やreturn文も省略できます。

図19:図18のコードからブロックとreturnを省略 図19:図18のコードからブロックとreturnを省略
※クリックすると拡大画像が見られます

 まだ続きます。実はJava SE 8では、ラムダ式で既存のメソッドを呼び出す場合、「メソッド参照」という記法によって記述を簡略化できます(ここでは、詳細な説明は割愛します)。その結果、次のようになります。

図20:メソッド参照 図20:メソッド参照
※クリックすると拡大画像が見られます
図21:メソッド参照を使い、図19のコードを簡略化 図21:メソッド参照を使い、図19のコードを簡略化
※クリックすると拡大画像が見られます

 以上の内容を反映して最終的に出来上がるコードは、次のようになります。

 いかがでしょうか。いきなりこのプログラムを見たら、これまでのJavaとはまったく違う形式なので驚かれるかもしれません。しかし、ここまでに説明した段階を1つ1つ見ていけば、どこでどのような処理をしているのか、何をどう省略しているのかをご理解いただけると思います。

クイックポール

WebLogic Channelで最も読んでみたい記事のテーマを以下よりお選びください