4月4日と5日の2日間に渡って開催された日本オラクル主催のJava技術者向けイベント「JavaOne Tokyo 2012」では、全国からJava開発者が集まり、Java周辺の最新動向や技術的なTips、活用事例などの話題に盛り上がった。
レポートの前編では、Java SE/EE/MEそれぞれの現状と今後の動向に焦点を当てて紹介した。後編は、Java SE/JDKについて、「Project Lambda」「Project Jigsaw」、そして新しいJavaScriptエンジンである「Nashorn」に関するセッションの様子をレポートする。
Java SE 8で予定されている大規模な機能拡張の1つに「ラムダ式(クロージャ)」の導入がある。Javaへのクロージャの導入はJava SE 7の開発当初から計画が持ち上がっていたが、長い期間にわたって議論がまとまらず、その内容も二転三転した経緯がある。
もともと、この機能拡張は「クロージャの導入」として知られており、標準仕様の提案である「JSR 335」でも「クロージャ」という記述がある。しかし、関数型がないことや、レキシカルスコープの扱いが制限されていること(可変の変数にアクセスできない)など、他の言語で一般的に「クロージャ」と呼ばれている機能とは少々異なる。従って、本稿では便宜上「ラムダ式」もしくは「クロージャ」と記載するが、上記の点に留意していただきたい。
さて、JavaOne Tokyoでは、オラクルのJava Platform GroupでPrinciple Member of Technical Staffを務めるDavid Holmes氏がセッション「Project Lambda: To Multicore and Beyond」において、このラムダ式の実装を進める「Project Lambda」を紹介した。
Holmes氏によれば、Project Lambdaの主な目的は、使いやすい並列処理ライブラリを提供すること、そして並列処理可能なコードをもっと簡易に記述できるようにすることだという。
Java SE 5からは「Concurrency Utilities」によって並行プログラミングのためのライブラリが充実した。そしてJava SE 7では「Fork/Join Framework」(JSR 166y)によって小さなタスクの並行処理が少ないオーバーヘッドで実現できるようになった。
しかし、現在のJavaでタスクを作成しようとすると、無名クラスを使った煩雑なコードになりやすい。その辺りの問題の解決を目指しているのがProject Lambdaというわけだ。
具体的には、Project Lambdaでは次の項目に関する仕様策定を進めている。
以下、1つ1つ解説しよう。
【1】ラムダ式
ラムダ式の対象となるのが、「Functional Interface」と呼ばれる、メソッドを1つだけ持てるインターフェイスである。Functional Interfaceの例としては、次のようなものが挙げられている。
このFunctional Interfaceのインスタンスが、ラムダ式を使うことで簡単に生成できるようになる。例えば、上の「Comparator」のインスタンスを生成したい場合、従来どおり無名クラスを使うと、次のようになる。
これが、ラムダ式を使えば次のように記述できる。「->」の左側が「compare()」に渡すパラメータ、右側が実行されるコードである。
なお、パラメータの型は明らかな場合には省略することもできるという。この例の場合、Compareの型パラメータが「String」であることから、パラメータの型も「String」であることが分かるため、省略して次のようにも書ける。
【2】メソッド参照
「メソッド参照」は、実行コード内でメソッドを呼び出す場合に、その参照をよりシンプルに記述できるようにするためのものである。例えば、読み込み可能なファイルのみをフィルタリングする次のようなコードを考えてみる。
これをラムダ式を使って書くと次のようになる。
メソッド参照を使うと、次のようにさらに短く記述できるようになる。
ラムダ式を活用して、最初の目的である並列処理が可能なライブラリを実現するには、どうすればよいのだろうか。並列処理の対象としては、ループ処理が最も重視されている。ループ処理を並列化するには、「内部イテレータ」を導入する方法がある。例として挙げられたのは次のようなコードだ。
これは、2011年に卒業した学生の成績から最大値を探すコードである。「filter()」メソッドは、コレクションの各要素から「Predicate」の「op()」メソッドの戻り値が「true」であるものだけを「Iterable」オブジェクトとして返す。「map()」メソッドは、同様に「Mapper」の「map()」メソッドの戻り値を要素とした「Iterable」オブジェクトを返す。
ラムダ式を使うと、このコードが次のようになり、非常にシンプルに記述できることが分かる。
【3】デフォルトメソッド
では、「filter()」「map()」などのメソッドはどのように実装すればいいのだろうか。これらのメソッドの定義をコレクションのインターフェイスに追加してしまうと、そのインターフェイスを実装したクラスすべてを修正しなければならず、互換性を損ねてしまう。そこで、インターフェイスのメソッドのデフォルトの実装を追加できる仕組みが導入される。これが「デフォルトメソッド」だ。
具体的には、次のようにメソッド定義に「default」というキーワードを付け、その後ろにデフォルトとして使用する実装を記述する。もし「sort()」メソッドが実装されなかった場合には、自動的にデフォルトの実装が使われるわけだ。
デフォルトメソッドの導入は、メソッドの実装が衝突してしまう危険が生じる。例えば、次のように「m()」というデフォルトメソッドを持ったインターフェイスAとBがあるとする。
AとBを実装するクラスCを作った場合、どちらのm()の実装が適用されるのかがはっきりしない。この場合、次のように「m()」を実装することで、使用する実装(この例ではAの実装)を明示的に指定するのだという。
デフォルトメソッドの適用例としては、Iterableインターフェイスへの次のようなメソッドの追加が挙げられた。
並列処理への対応が、ラムダ式導入の本質
なお、【2】の内部イテレータの処理を並列化するにはどうしたらいいだろうか。それには、次のように「parallel()」メソッドの処理を追加するだけでいいそうだ。
並列処理については、その他にも「Iterable」インターフェイスの並列処理版である「Spliterable」といったインターフェイスも提案されている。
このように、Javaへのラムダ式(クロージャ)の導入は、単に新しい文法が追加されるということではなく、並列処理への対応が、その本質だ。Java開発者はこの点に注目して、Java SE 8への心構えを始めていく必要があるだろう。
Copyright © ITmedia, Inc. All Rights Reserved.