はじめに
ラムダ式って初見だと読めない独特なスタイルですよね。
ちょっと整理してみます。
1.ラムダ式の基本形
こんなやつ
Runnable r = () -> System.out.println("OK");
// (引数リスト) -> メソッドの実装
// 引数ありバージョン
Consumer<String> consumer = (str) -> System.out.println(str);
// 戻り値ありバージョン
Supplier<String> supplier = () -> {return "OK"};
2.なんのため?
匿名クラスの実装を簡潔に記述するために利用する。
ちょい脱線 「匿名クラス(無名クラス)」とは何か
まず、ローカルクラスというメソッドの処理中にクラスを宣言して利用できる仕組みがある。
ローカルクラス
public static void main(String[] args) {
class Local implements Runnable { // メソッド内でクラスを宣言(Runnableインタフェースを継承)
@Override
public void run() { // Runnableインタフェースのrunメソッドをオーバーライド
System.out.println("OK");
}
}
Runnable r = new Local(); // 宣言したローカルクラスをnewして利用する
r.run(); // 「OK」が出力される
}
そのローカルクラスの「宣言からnewして利用するまで」の流れのうち、宣言部分を省略して記述するのが匿名クラス
匿名クラス
public static void main(String[] args) {
Runnable r = new Local() { // クラスの宣言を省略し、直接new(利用)する
@Override
public void run() { // Runnableインタフェースのrunメソッドをオーバーライド
System.out.println("OK");
}
}
r.run(); // 「OK」が出力される
}
匿名クラスをさらに省略したのがラムダ式
ラムダ式
public static void main(String[] args) {
Runnable r = () -> { System.out.println("OK"); };
r.run(); // 「OK」が出力される
}
3.ラムダ式の利用条件
ラムダ式で使用できるのは抽象メソッドが一つのインターフェースのみ
以下のメソッド宣言の部分を省略しているため、インタフェースに抽象メソッドが複数あるとどれをオーバーライドしているのかがわからなくなる
@Override
public void run() {
SE8から新しくjava.util.functionパッケージ以下に単一のメソッドをもつインターフェース群が追加されており、これらは関数型インタフェースと呼ばれ、ラムダ式で利用できる。
関数型インタフェースについてはまた別の機会に。。。
4.匿名クラス〜ラムダ式になるまでをもう少し詳しく
匿名クラス
public static void main(String[] args) {
Runnable r = new Local() { // クラスの宣言を省略し、直接newする
public void run() { // Runnableインタフェースのrunメソッドをオーバーライド
System.out.println("OK");
}
};
r.run(); // 「OK」が出力される
}
ラムダ式への変換①
public static void main(String[] args) {
Runnable r = //(new Local() {) 型推論により省略
public void run() {
System.out.println("OK");
}
//}
r.run(); // 「OK」が出力される
}
ラムダ式への変換②
public static void main(String[] args) {
Runnable r = //(new Local() {) 型推論により省略
// (public void run() {) 抽象メソッドは1つしかないため、どのメソッドかは自明であり省略
System.out.println("OK");
// }
// }
r.run(); // 「OK」が出力される
}
ラムダ式への変換③
残った部分をぎゅっと集めて
public static void main(String[] args) {
Runnable r = System.out.println("OK");
r.run(); // 「OK」が出力される
}
↓↓↓ ラムダ式の書き方を適用
public static void main(String[] args) {
Runnable r = () -> System.out.println("OK");
// アロー演算子「->」をつけて、中カッコ「{}」をとる。(returnが必要ない場合は中カッコを取る必要がある。)
r.run(); // 「OK」が出力される
}
5.ラムダ式の「()」や「{}」の省略事情
- 引数リストの省略
引数が1つの場合、カッコを省略することができる。
// 省略前
(String s) -> {
System.out.println(s);
}
// 省略後
s -> {
System.out.println(s);
}
- ブロックの省略
戻り値の型がメソッド引数の型と一致し、かつ、処理が1行のみの場合、ブロックとreturnキーワードを省略することができる。
// 省略前
(String s) -> {
return s.toUpperCase();
};
// 省略後
s -> s.toUpperCase();
- 型注釈の省略
関数型インターフェースの型注釈が明らかであれば、型注釈を省略することができる。
// 省略前
Comparator<String> comparator = (String s1, String s2) -> s1.compareTo(s2);
// 省略後
Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
結論
ラムダ式は慣れないと可読性が絶望的
でも使えるとつよつよ感が出て気持ちいい。