株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ Java SE 8 のラムダ式について 目次 1 2 3 ラムダ式とは?...................................................................................................... 2 1.1 ラムダ式の概要 ............................................................................................... 2 1.2 ラムダ式のメリット ........................................................................................ 2 環境の構築............................................................................................................. 4 2.1 JDK8 のインストール ..................................................................................... 4 2.2 IDE(統合開発環境)の構築(NetBeans) ................................................... 5 ラムダ式の使い方 ................................................................................................ 13 3.1 ラムダ式の基本的な記述方法 ........................................................................ 13 3.1.1 匿名クラスを使った場合 ........................................................................ 14 3.1.2 ラムダ式を使った場合 ............................................................................ 15 3.2 匿名クラスとラムダ式での違い .................................................................... 17 3.2.1 this が表すもの ..................................................................................... 17 3.2.2 匿名クラス内の変数やメソッドが使えない ............................................ 19 3.3 ラムダ式の応用 ............................................................................................. 19 3.3.1 条件式が入ったラムダ式 ........................................................................ 19 3.3.2 複数の関数型インターフェイスを定義する場合 ..................................... 21 4 まとめ.................................................................................................................. 24 5 参考文献 .............................................................................................................. 25 -1- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 1 ラムダ式とは? 1.1 ラムダ式の概要 2014 年 3 月に一般公開予定の Java SE 8 で取り入れられる機能の一つにラムダ式 (Lambda)があります。このラムダ式とは Java SE 8 で取り入れられる Java にとっ ては新たな表記方法です。またラムダ式は Java コミュニティではクロージャ(Closure) とも呼ばれています。 ラムダ式は関数型インターフェイス(一つのインターフェイスに一つだけ実装が必要 なメソッドを持つインターフェイス)のメソッドを実装する際に使うことができ、下記 の基本文法に従って処理の実装を簡潔に記述できるようになります。 ( 実装するメソッドの引数 ) -> 処理 ※引数は複数設定可能 また、Java SE 8 ではラムダ式と共にインターフェイスが関数型インターフェイスで あることを示すアノテーション「@FunctionalInterface」を取り入れます。このアノテ ーションはインターフェイスが関数型インターフェイスであることを定義するための ものです。例えば@FunctionalInterface を定義しているインターフェイスに複数の実 装すべきメソッドを持たせようとして関数型インターフェイスではなくなった場合、コ ンパイルエラーになります。ラムダ式を使うぶんにはインターフェイスが関数型インタ ーフェイスである限り@FunctionalInterface がなくても実装できますが、後で対象の インターフェイスに別のメソッドを加えたりされるのを防ぐためにもこのアノテーシ ョンは付けておくのが無難かと思われます。 1.2 ラムダ式のメリット ラムダ式を取り入れるメリットは、今まで関数型インターフェイスの実装のために書 いていた助長なソースをラムダ式によってシンプルに書くことができるようになる点 です。Java SE 8 より前の実装のように匿名クラスを使った場合、Override しなけれ -2- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ ばいけないメソッドの宣言など不要な行が増えたり、何層もの深いインデントができた りすることになり、ソースが読みづらくなってしまいます。例えば Button オブジェク トに ActionListener を追加した場合に、匿名クラスとラムダ式を比べると、次のよう になります。 匿名クラスの場合: Button button = new Button(); button.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { System.out.println("e=" + e.toString()); } }); ラムダ式の場合: Button button = new Button(); button.addActionListener( e -> System.out.println("e=" + e.toString())); このようにラムダ式を使うことによって匿名クラスを使った実装より簡潔に書くこ とができます。 本稿では Java での経験、特に匿名クラスの実装の経験がありラムダ式を初めて使う 人を対象に、ラムダ式の使い方を簡単に解説していきます。そのため、匿名クラスの仕 様を知っている前提で解説するので今回のレポートでは匿名クラスの仕様やラムダ式 と匿名クラスの差異がないところなどは特に説明していません。 -3- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 2 環境の構築 2.1 JDK8 のインストール 下記の URL より JDK8 をダウンロードします。 https://jdk8.java.net/download.html JDK をダウンロードしたらそのファイルが実行ファイルの場合はそのファイルを実 行し、圧縮ファイルの場合はそのファイルを解凍し任意のディレクトリに配置してくだ さい。インストールの際にライセンス契約条件を読み、同意した場合は特別な設定が必 要な環境ではない限り、デフォルトの設定のまま進めてインストールできます。 -4- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 2.2 IDE(統合開発環境)の構築(NETBEANS) 現在 (2013 年 12 月執筆時)、 ラムダ式を実装できる環境はまだ限られています。JDK8 の対応を実験的に行っているものも含めて、現在使える開発環境には下記のものがあり ます。 NetBeans IntelliJ IDEA e(fx)clipse Project Lambda(http://openjdk.java.net/projects/lambda/)によると、この中で IntelliJ IDEA と e(fx)xlipse は Java SE 8 の対応はまだ実験的な段階なので、今回は NetBeans を使って開発環境を構築します。 また、Project Lambda によると NetBeans 自体は JDK7で実行するように書かれて いますが、サンプルを試す分には JDK8 のみで問題なかったので、今回は JDK8 のみ インストールした状態で環境構築しています。 ※ 以下、プラットフォームが Windows7 の場合の例です。 -5- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 手順1: 下記の URL より NetBeans をダウンロードしてください。今回は簡単なサンプルを 試すだけなので必要最低限の機能を持つ「Java SE」を選択します。 https://netbeans.org/downloads/ -6- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 手順2: ダウンロードしたインストーラを実行します。 -7- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 手順3: ライセンス契約条件を読み、同意する場合はチェックをし、「次(N)」ボタンを押下 します。 -8- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 手順4: JUnit のライセンス契約条件に同意し JUnit も一緒にインストールする場合は「ライ センス契約条件に同意する。(A)JUnit をインストール」を選択し、インストールしな い場合は「JUnit をインストールしない(D)」を選択します。今回のサンプルでは JUnit をインストールしなくても問題ありませんがデフォルトのままチェックをいれていま す。 -9- 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 手順5: NetBeans のインストール先と JDK を指定します。JDK の指定は Java8 の機能が使 えるようにインストールした JDK8(※ フォルダは jdk1.8.0 となっている)のディレ クトリを指定します。 - 10 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 手順6: NetBeans のプラグインに新しいバージョンが出たか自動で確認する場合には「更新 の確認(U)」にチェックを入れます。今回はチェックを入れなくても問題ありませんが、 デフォルトのままチェックを入れた状態にしています。 - 11 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 手順7: NetBeans の統計データを NetBeans プロジェクトに匿名で送信しても問題がない場 合にチェックを入れます。今回はチェックを入れなくても問題ありませんが、デフォル トのままチェックを入れた状態にしています。 - 12 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 3 ラムダ式の使い方 3.1 ラムダ式の基本的な記述方法 ラムダ式は前述したように関数型インターフェイスのメソッドに対して、下記の書式 で実装します。 ( 実装するメソッドの引数 ) -> 処理 1. 左辺に実装するメソッドの引数を記述 2. 「->」を記述 3. 右辺に実装する処理(ターゲット式)を記述 (ア) 1つの式や 1 行で表せる式のみの場合は「{}」は不要 ① 戻り値の有無に関わらず「return」は記述不可 (イ) 複数の式で成り立つ場合は「{}」の間に処理を記述 ① 戻り値がある場合は「return」が必要 ② 戻り値がない場合は「return」が不要 ラムダ式で処理の実装をする際に匿名クラスの実装と同じことを記述することによ って匿名クラスが行っていることと同じことをいくつかの違いを除いてできるように なります。このいくつかの違いについては後ほど記述します。 ここからは、匿名クラスの実装からどのようにラムダ式に変換できるのかを示してい きます。まず、例として対象の文字列が数値の場合は True を返す関数型インターフェ イスがあったとします。この関数型インターフェイスのメソッドを対象の文字列が数値 かを判別し標準出力するように実装します。 ※ 以下、文中のコードでは packege および import 文やコメントは省略しています。 - 13 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ IsNumberFunction.java @FunctionalInterface public interface IsNumberFunction { boolean check(String value); } 3.1.1 匿名クラスを使った場合 匿名クラスを使って Interface を実装した場合は次のようになります。 Sample0101.java public static void main(String[] args) { IsNumberFunction isNumberFunction = new IsNumberFunction() { @Override public boolean check(String value) { try { new BigDecimal(value); return true; } catch (Exception e) { return false; } } }; String checkValue = "1"; System.out.println("結果=" + isNumberFunction.check(checkValue)); } このクラスを実行すると下記の結果が標準出力されます。 結果=true - 14 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ また、先のソースの checkValue を「“あ”」にすると下記の結果が標準出力されます。 結果=false 3.1.2 ラムダ式を使った場合 次に前述の匿名クラスで実装したものを単純にラムダ式に変換したものを示します。 Sample0102.java public static void main(String[] args) { IsNumberFunction isNumberFunction = (String value) -> { try { new BigDecimal(value); return true; } catch (Exception e) { return false; } }; String checkValue = "1"; System.out.println("結果=" + isNumberFunction.check(checkValue)); } ここではラムダ式の左辺に引数の型を記述していますが、この引数の型は省略するこ とが可能です。また引数が一つの場合は左辺の「()」も省略することが可能です。 Sample0103.java IsNumberFunction isNumberFunction = value -> { try { ・・・ ただし、もし実装するメソッドに引数がない場合は「()」を省略することはできませ ん。その場合、下記のように記述します。 - 15 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ () -> 処理 またラムダ式では処理の記述に static のメソッドを使うことも可能です。例えば NumberUtil というクラスに同じ処理をする static の isNumber メソッドがあったとし ます。この場合、ラムダ式では次のように記述することが可能です。 Sample0104.java public static void main(String[] args) { IsNumberFunction isNumberFunction = value -> { return NumberUtil.isNumber(value); }; String checkValue = "1"; System.out.println("結果=" + isNumberFunction.check(checkValue)); } また、この処理は一つの式しかないので「{}」を外して次のように記述することも可 能です。そしてこの場合は戻り値の有無に関わらず「return」は記述しません。 Sample0105.java public static void main(String[] args) { IsNumberFunction isNumberFunction = value -> NumberUtil.isNumber(value); String checkValue = "1"; System.out.println("結果=" + isNumberFunction.check(checkValue)); } また、クラス内のメソッドも呼び出すことも可能です。 Sample0106.java public class Sample0106 { - 16 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ public static void main(String[] args) { Sample0106 sample = new Sample0106(); sample.process(); } private void process(){ IsNumberFunction isNumberFunction = value -> isNumber(value); String checkValue = "1"; System.out.println("結果=" + isNumberFunction.check(checkValue)); } private boolean isNumber(String value){ return NumberUtil.isNumber(value); } } また、注意事項としてラムダ式の制限の一つとして引数にアンダーバーのみのものは 使えません。下記はコンパイル・エラーになります。 (_) -> 処理(ターゲット式) 3.2 匿名クラスとラムダ式での違い ラムダ式は基本的に匿名クラスの実装を別の書式で書き表しているようなものなの で匿名クラスの実装でできることはラムダ式でもほぼできます。ただし下記については 匿名クラスとラムダ式の間で違いがあります。 3.2.1 this が表すもの 匿名クラスの実装部分で書かれた this はその匿名クラス自身を表しますがラムダ式 の場合の this はそのラムダ式が記述されたクラス自体を表します。例えば引数がなく 標準出力するのみ行うための関数型インターフェイスを下記のように実装した場合、匿 - 17 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 名クラスの場合の this は匿名クラスの Sample0201$1 を表し、ラムダ式の this は Sample0201 を表します。 Sample0201.java public class Sample0201 { /** * @param args the command line arguments */ public static void main(String[] args) { Sample0201 sample = new Sample0201(); sample.process(); } private void process() { // 匿名クラスの場合 OutputFunction anonymous = new OutputFunction() { @Override public void output() { System.out.println("匿名クラス:this.getClass()=" + this.getClass()); } }; anonymous.output(); // ラムダ式の場合 OutputFunction lambda = () -> { System.out.println("ラムダ式:this.getClass()=" + this.getClass()); }; lambda.output(); } - 18 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ @FunctionalInterface interface OutputFunction { void output(); } } 実行結果は次のようになります。 匿名クラス:this.getClass()=class jp.co.bbreak.lambda.sample02.Sample0201$1 ラムダ式:this.getClass()=class jp.co.bbreak.lambda.sample02.Sample0201 3.2.2 匿名クラス内の変数やメソッドが使えない これは実装を見れば一目瞭然なのですが、ラムダ式は直接処理の実装を行うので、匿 名クラスの場合は持てた匿名クラス内の変数やメソッドを持つことができなくなりま す。次の場合、ラムダ式だと msg が実装できません。 Sample0202.java // 匿名クラスの場合 OutputFunction anonymous = new OutputFunction() { String msg = "匿名クラス内の変数"; @Override public void output() { System.out.println("msg="+msg); } }; 3.3 ラムダ式の応用 3.3.1 条件式が入ったラムダ式 ラムダ式では右辺のターゲット式を条件分岐で選択することができます。例えば引数 の値が Null だと 0 を返し、Null でない場合はその文字数を返す関数型インターフェイ スを実装する場合に下記のように記述することができます。 - 19 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ Sample0301.java public static void main(String[] args) { LengthFunction function = value -> value == null ? 0 : value.length(); String checkValue = "あいうえお"; System.out.println("結果=" + function.getLength(checkValue)); } @FunctionalInterface private interface LengthFunction { int getLength(String value); } これは次のように{}ブロックで書き換えることも可能です。 Sample0301Statements.java LengthFunction function = value -> { if (value == null){ return 0; } else { return value.length(); } }; ただし、下記の OutputFunction の実装のようにターゲット式が戻り値のない void だった場合、条件式が入ったラムダ式を書くとエラーになります。 例: public static void main(String[] args) { ・・・ OutputFunction function = value -> value == null ? System.out.println("value is null") : System.out.println(value); - 20 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ ・・・ } @FunctionalInterface private interface OutputFunction { void output(String value); } ブロックで下記のように書かれているとエラーにはなりません。 Sample0301Void.java public static void main(String[] args) { OutputFunction function = value -> { if (value == null) { System.out.println("value is null"); } else { System.out.println(value); } }; String checkValue = "あいうえお"; function.output(checkValue); } @FunctionalInterface private interface OutputFunction { void output(String value); } 3.3.2 複数の関数型インターフェイスを定義する場合 関数型インターフェイスの中には戻り値が特定のクラスやインターフェイスのイン スタンスを返すものを作成することも可能です。もし、その戻り値も関数型インターフ ェイスの場合、呼び出し元の関数型インターフェイスおよびその戻り値の関数型インタ ーフェイスも一緒に実装することが可能です。例えば GetFunction インターフェイス が関数型インターフェイスである OutputFunction インターフェイスを返し、その - 21 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ OutputFunction インターフェイスが「Hello」と標準出力するように実装する場合、 次のようになります。 Sample0302.java public static void main(String[] args) throws Exception { OutputFunction outputFunction = call( () -> () -> System.out.println("Hello")); outputFunction.output(); } private static OutputFunction call(GetFunction<OutputFunction> getFunction){ return getFunction.get(); } @FunctionalInterface private interface GetFunction<T> { T get(); } @FunctionalInterface private interface OutputFunction { void output(); } この GetFunction インターフェイスの実装をラムダ式から匿名クラスに変えると次 のようになります。ここでは OutputFunction の実装のみをラムダ式で行っています。 Sample0302Anonymous01.java public static void main(String[] args) throws Exception { OutputFunction outputFunction = call( new GetFunction<OutputFunction>() { @Override public OutputFunction get() { - 22 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ return ()->System.out.println("Hello"); } }); outputFunction.output(); } 最後にラムダ式を使わず全て匿名クラスで実装すると次のようになります。 Sample0302Anonymous02.java public static void main(String[] args) throws Exception { OutputFunction outputFunction = call(new GetFunction<OutputFunction>() { @Override public OutputFunction get() { return new OutputFunction() { @Override public void output() { System.out.println("Hello"); } }; } }); outputFunction.output(); } - 23 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 4 まとめ 今回のレポートでは Java SE 8 に追加される機能の一つであるラムダ式について見 てきました。基本的には匿名クラスで煩雑になっていた実装がラムダ式を使うことによ って簡易化されて実装しやすくなることが想像できると思います。 また、あえてラムダ式を導入せず今までのように匿名クラスを使った実装も行えるの でラムダ式をあえて知ろうと思わない人もいるかもしれません。しかし、今後、ラムダ 式が多く開発者に使われるようになっていくと、知りたい情報がラムダ式で書かれたり、 他の開発者が書いたプログラムを引き継いだ際にラムダ式を使っていたりと、いたると ころでラムダ式が出てくる可能性が高くなります。その際に最低でもラムダ式を読むこ とができないと開発者にとって大きな問題になります。そのためにも Java を扱う開発 者はラムダ式についての知識は必要不可欠になるかと思います。 ただし懸念事項としてラムダ式が実装を簡易化した分、ある程度のコーディング・ル ールを持たないと後々そのソースを見た際に何がやりたかったのかわからなくなる可 能性やバグが発生しやすく原因が分かりにくい実装なども出てくる可能性があると思 います。今後はラムダ式のメリットを生かしつつ問題が起きにくい実装方法を考えてい くのがこれからのラムダ式の課題になっていくのかもしれません。 - 24 - 株式会社ビーブレイクシステムズ http://www.bbreak.co.jp/ 5 参考文献 JDK 8 Project http://openjdk.java.net/projects/jdk8/ Project Lambda http://openjdk.java.net/projects/lambda/ NetBeans https://netbeans.org/ Lambda Expressions (The Java Tutorials > Learning the Java Language > Classes and Objects) http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html Method References (The Java Tutorials > Learning the Java Language > Classes and Objects) http://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html When to Use Nested Classes, Local Classes, Anonymous Classes, and Lambda Expressions (The Java Tutorials > Learning the Java Language > Classes and Objects) http://docs.oracle.com/javase/tutorial/java/javaOO/whentouse.html Maurice Naftalin's Lambda FAQ http://www.lambdafaq.org/ Project Lambda の基礎 http://www.slideshare.net/skrb/project-lambda-24531410 開発部 - 25 - 長谷川 智之
© Copyright 2024