Java SE 8では、Java言語に新しいシンタックスとして「ラムダ式」(Lambda Expression)が導入された。ラムダ式を使うと、従来よりも短いコードで、処理の流れが明瞭なコードを書くことができる。本稿では、Java Magazineのバックナンバーからラムダ式の習得に役立つ選りすぐりの記事をご紹介しよう。
「Java 8:ラムダ式」Vol.12(P30)
ラムダ式をこれから学習する人に、まず読んでほしいのがJava Magazine 第12号に掲載されたTed Neward氏によるこの記事だ。この記事の冒頭では、ラムダ式の導入が必要と判断されるにいたった背景が解説されている。すなわち一連のコード・ブロックをオブジェクトとして渡す、関数型プログラミングの考え方を導入することが、Javaの生産性向上に有効だと判断され、それを実現するための言語機能として、ラムダ式が導入されたのである。
以下、ラムダ式の効用を端的に紹介している箇所を引用しよう。
ラムダ式:ラムダ式は基本的には、後で実行するメソッドの実装を簡潔に記述するための手法です。たとえば、以前はリスト2に示すようにRunnableを定義しました。リスト2では、匿名インナー・クラス構文を使用していますが、基本的な概念を表現するコード行が過度に多くなるという「縦の問題(verticalproblem)」が明らかに見られます。一方、Java 8のラムダ式構文では、同じ内容のコードをリスト3のように記述できます。
<リスト2>
public class Lambdas {
public static void main(String... args) {
Runnable r = new Runnable() {
public void run() {
System.out.println("Howdy, world!");
}
};
r.run();
}
}
<リスト3>
public static void main(String... args) {
Runnable r2 = () -> System.out.println("Howdy, world!");
r2.run();
}
いずれのコードも結果は同じで、Runnable実装オブジェクトのrun()メソッドが呼び出され、コンソールに何らかの文字列が出力されます。しかし、内部的には、Java 8で記述したリスト3のコードではRunnableインタフェースを実装する匿名クラスを生成する以外にも、少し処理が加えられます。その処理の一部は、Java 7で導入されたinvoke dynamicバイトコードに関係します。本記事では詳細には説明しませんが、ラムダ式構文の結果、単なる匿名クラスのインスタンス以上のものができると覚えておきましょう。
この記事では、関数型インタフェース、構文、型推論、レキシカル・スコープなど、ラムダ式を理解するために必要な基本概念を、短いサンプル・コードを用いて解説している。
「Java 8:ラムダ式」Vol.13(P16)
Java Magazine 第13号のこの記事は、上で紹介したTed Neward氏の記事の続編だ。Java SE 8では、ラムダ式の導入に併せて、既存のコア・ライブラリがラムダ式対応に改修されている。また、ラムダ式をより有効に利用できるよう、新たなAPIやインタフェース、クラスが追加された。この記事では、そうしたラムダ式がもたらす変化がJavaライブラリに与えた影響について詳細に説明されている。たとえば、Collections APIでは要素を比較するためのComparatorインタフェースが大幅に強化されているほか、ストリームデータを扱うためのStream APIも追加されている。
以下、Stream APIを解説している箇所の冒頭部分を引用しよう。
ストリーム:JDK内の他のインタフェースと同様に、Stream インタフェースもCollections API を含むさまざまなシナリオでの使用を想定した基本的なインタフェースです。Stream インタフェースはオブジェクトのストリームを表しており、表面上は、Iteratorと同様に、コレクション内部のオブジェクトに1 つずつアクセスします。しかし、コレクションとは異なり、Stream では、オブジェクトのコレクションが有限でない場合があります。そのため、ファイルから文字列を取得する操作などの、さまざまな種類のオンデマンド操作に使用できる選択肢となります。関数合成が可能であるのに加えて、「内部的に」並列化を実行できるように、ストリームが設計されているからです。
「ラムダ式を使用した高度な操作」Vol.11(P33)
最後に紹介するこの記事は、第8号、第10号、第11号に掲載された、Ben Evans氏とMartijn Verburg氏による全3回の連載記事の最終回だ。この回では、Stream APIのより実践的な利用方法が解説されている。
上述のように、Stream APIはラムダ式とともにJava SE 8で登場した新機能であり、ラムダ式を前提とした設計がなされている。従来のコレクション操作では困難だったデータ処理を可能にする。ストリームは、データセットが確定していない状態でも使用することができ、遅延評価による高度な並列化が可能だ。
以下、Stream APIを利用した並列化について言及している箇所を引用しよう。
Project Lambda の主な目標の1 つは、Java におけるコレクションのサポートをアップグレードして、マルチコア・プロセッサを効率的に利用できるようにすることです。
for ループは直列的です。 Java 7 以前のバージョンでは、コレクションに対するすべての操作も直列的です。 つまり、操作するコレクションの大きさを問わず、操作の実行に使用されるコアは1つしかありません。 これは、データ・セットが大きくなるほど不都合です。
ストリームの遅延評価アプローチによって、ラムダ式フレームワークで並列操作をサポートできるようになります。
Stream API の主な前提事項は、「Collection から作成する場合もその他の手法で作成する場合も、Stream の作成にはコストをかけるべきではない。ただしパイプライン内にコストのかかる操作が含まれる場合もある」というものです。 このような前提があるため、以下のような並列パイプラインを記述することができます。
s.stream()
.parallel()
.streamOps()
.collect();
parallel() という1つのメソッドにより、それまで直列的であったStreamが並列化されます。 この並列化機能により、一般の開発者はparallel() を並列化のエントリ・ポイントとして使用できます。並列化サポートの提供はライブラリ作成者の担当となります。
また、本記事では、複数のストリームを統合する際に利用するSpliteratorインタフェースについても解説を加えている。
なお、この連載のPart1(第8号:P31)とPart2(第10号:P36)には、上で紹介したTed Neward氏の記事と重複する内容が含まれているものの、ラムダ式への理解を深めるうえで有用なので、ご一読されることをお勧めしたい。