Java8より追加されたラムダ式ですが、理解せずに何となく使っていたり、うまく使いこなせていなかったりしないでしょうか。
プログラムはシンプルであるほど、読みやすく、バグを少なくできます。
ラムダ式もプログラムをシンプルにすることができる1つの方法となりますが、Java7以前の記述方法でも動作上は変わらないので使いこなしている方が少ないように見えます。
本記事では、Javaのラムダ式の入門ということで、一緒に説明されることの多いStreaming APIとは切り離して、ラムダ式だけを解説します。
記事内に記載しているプログラムは、Java11を使って動作確認をしています。
Javaのラムダ式入門
Javaのラムダ式として、以下の内容を解説します。
- ラムダ式とは
- これまでの記述方法との違い
- ラムダ式の構文
- ラムダ式の注意点
ラムダ式とは
ラムダ式はJava8から追加された文法です。
アロー演算子「->」を使って表現するのが大きな特徴となります。
ラムダ式を理解するには、関数型インタフェースの理解が必要となります。
関数型インタフェースとは
関数型インタフェース(functional interface)は、抽象メソッドを1つだけ持つインタフェースです。
インタフェース定義に @FunctionalInterface アノテーションを付けることで、関数型インタフェースとなります。
自分で作成したインタフェースもアノテーションを付けることで、関数型インタフェースにすることができます。
Javaの標準APIとして提供されているインタフェースでも関数型インタフェースはたくさんあり、例えば以下のようなインタフェースがあります。
- java.lang.Comparable
- java.lang.Runnable
- java.util.Comparator
ラムダ式は、この関数型インタフェースをシンプルな記述方法で実装し、変数として表すことができるものになります。
これまでの記述方法との違い
サンプルプログラムとして、以下の関数型インタフェースをラムダ式と従来の方法で実装してみます。
@FunctionalInterface interface Calc { int add(int a, int b); }
addというメソッドを1つだけ持つ関数型インタフェースです。
これを従来の実装方法で行うと以下のようになります。
class CalcImpl implements Calc { public int add(int a, int b) { return a + b; } } Calc c = new CalcImpl(); System.out.println(c.add(1, 2)); // -> 3
上記のプログラムは、ローカルクラスとして実装しています。
クラスを実装した後に、newでインスタンス化してメソッドを呼出しています。
次も従来の実装方法として、匿名クラスを使って実装してみます。
Calc c2 = new Calc() { public int add(int a, int b) { return a + b; } }; System.out.println(c2.add(1, 2)); // -> 3
newと同時に実装するので、ローカルクラスよりもやや短いステップで実装ができます。
最後に、ラムダ式を使った実装が以下になります。
Calc c3 = (a, b) -> a + b; System.out.println(c3.add(1, 2));
2行でシンプルに記述することができました。
ラムダ式の構文
ラムダ式の基本構文は以下のようになります。
(引数, …) -> { メソッドの処理 … }
ラムダ式の構文は大きく、引数部とメソッド処理部に別れます。
引数部
引数部は、括弧 () で括って、中に引数を列挙するのが基本的な構文です。
引数の型は、関数型インタフェースで明確になっていますので、実装時は省略することができます。
引数が1つの場合は、括弧を省略することが可能になります。
引数がない場合は、括弧は省略できません。
Sample arg0 = () -> 1 + 2; Sample arg1 = a -> a + 2; Sample arg2 = (a, b) -> a + b;
メソッド処理部
メソッド処理部は、ブロック {} の中に、処理を記述するのが基本的な構文です。
メソッドの内容が1行で書ける場合は、ブロックなしで記述することができます。
そして、戻り値があり、1行で書ける場合は、return文も省略することができます。
Sample exp1 = (a, b) -> a + b; Sample exp2 = (a, b) -> { int c = a + b; return c; }
ラムダ式の注意点
ラムダ式を扱う際に、いくつか注意点があります。
変数のfinal化
ラムダ式のメソッドの中では、クラスのフィールド変数や宣言済みのローカル変数を使うことができます。
その際に、ラムダ式の外にあるローカル変数はfinal化されるため、変更することができなくなります。
int result = 0; Calc c4 = (a, b) -> { result = a + b; // コンパイルエラーとなる return result; };
例えば上記のプログラムは、ローカル変数resultに、ラムダ式の中で代入していますが、これはコンパイルエラーとなります。
このように、final修飾子がついたような扱いとなりますので、参照のみ可という状態になります。
ただし、以下のように配列やオブジェクトの中身は書き換えることが出来てしまうので、それはそれでご注意ください。
int[] results = {0}; System.out.println(results[0]); // -> 0 Calc c5 = (a, b) -> { results[0] = a + b; // 変更できてしまう return results[0]; }; c5.add(1, 2); System.out.println(results[0]); // -> 3
変数のスコープ
匿名クラスの中でthisで参照した場合は、匿名クラス自身となりますが、ラムダ式の中でthisで参照すると作成したクラスとなります。
このことからラムダ式のクラスに定義されているフィールド変数は参照可能ですが、変数名を重複させるとローカル変数が優先されたり、ローカル変数同士の重複は出来ないなどのスコープに注意が必要です。
まとめ
Javaのラムダ式についてまとめると以下になります。
- ラムダ式とは、関数型インタフェースをシンプルな記述方法で実装できる構文。
- 引数の数や処理の長さによっては、括弧やブロックを省略して記述できる。
- ラムダ式外にある変数の参照や書換えには注意が必要。
Java8の目玉の1つでもある、Streaming APIはこのラムダ式を応用したものになります。
Streaming APIを扱うには、このラムダ式の理解がポイントとなります。
今回Javaのラムダ式について解説しました。
以上、参考になれば幸いです。
コメント