関数型プログラミングに第1級オブジェクト、高階関数。
ラムダ式のことを調べれば調べるほど、難解な言葉が沢山出てきてサッパリ分からない…。
今回は、上記のようにJavaのラムダ式学習で悩める方に向けて、極力難しい言葉を使わずに、易しく&分かりやすくラムダ式について解説していきます。
それでは、さっそく見ていきましょう。
Javaのラムダ式ってなんなのさ?
結論からお伝えすると、ラムダ式とは「匿名クラス」の実装を簡潔に実装できるものです。
まずはラムダ式を、実際に見てみましょう。
@FunctionalInterface
public interface Sample {
public void greeting();
}
public class Main{
public static void main(String[] args) {
Sample sample = () -> System.out.println("Hello!!");
sample.greeting();
}
}
上記のコードで行っていることは、以下の通りです。
- 関数型インターフェースSampleを定義する
- ラムダ式をSampleインターフェースに渡し(代入)、匿名クラスとして実装からインスタンス化までを行い、sample変数に格納する
- 匿名クラスが格納されたsampleからgreetingメソッドを呼び出す
はい。きっとラムダ式で悩める方にとっては、ちんぷんかんぷんですよね。
ラムダ式なんてくそくらえ!状態から脱却するためには、まずは 「匿名クラス」をしっかり理解することが重要になってきます。
次は、匿名クラスを詳しく見ていきましょう。
ラムダ式を理解するために匿名クラスを理解しよう
匿名クラスとは、クラス名が無く、その場で使い捨てるクラスを作りたい場合に使うものです。
無名クラスとも言われます。
それでは、匿名クラスが実際にどんな場面で使われて、どのように約に立つのかを解説していきます。
まずは、匿名クラスを利用しない場合のコードを見てみましょう。
// インターフェース
public interface Sample {
public void greeting();
}
// 実装クラス
public class SampleImpl implements Sample{
@Override
public void greeting() {
System.out.println("Hello!!");
}
}
// 実行クラス
public class Main{
public static void main(String[] args) {
Sample sample = new SampleImpl();
sample.greeting();
}
}
上記のコードを見ていただけると分かる通り、
- インターフェース(Sample)
- 実装クラス(SampleImple)
- 実行クラス(Main)
上記3つを利用することで、greetingメソッドを呼び出しています。
次に上記コードを、匿名クラスを利用して置き換えてみましょう。
// インターフェース
public interface Sample {
public void greeting();
}
// 実行クラス
public class Main{
public static void main(String[] args) {
Sample sample = new Sample() { // 匿名クラスとして処理を実装し、インスタンス化している
@Override
public void greeting() {
System.out.println("Hello!!");
}
};
sample.greeting();
}
}
上記のコードでは、実行クラス内で匿名クラスを使い、クラス宣言とインスタンス化を同時に行っています。
そして実装クラス(SampleImpl)を利用することなく、greetingメソッドを呼び出すことができ、余計なコードは排除されています。
ここまで見て、「なんで匿名クラスが必要になるの?」という疑問が浮かぶ方もいるでしょう。
その答えが、最初にお伝えした「その場で使い捨てるクラスを作りたい場合」に必要になるということです。
例えば、SampleImplで実装している「Hello!!」を出力するgreetingメソッドを、他で再利用する予定が無い場合。
// 実装クラス
public class SampleImpl implements Sample{
@Override
public void greeting() {
System.out.println("Hello!"); // Hello!!と出力するgreetingメソッドは他で使う予定がない...
}
}
1つのクラス内でしか使う予定がないのに、わざわざ実装クラスを用意するのは、手間でしかないですよね。
そこで匿名クラスを利用することで、実装クラスを用意することなく、実装からインスタンス化まで行うことができるという訳です。
匿名クラスとラムダ式を比較してみる
匿名クラスを理解したところで、ラムダの解説に戻ります。
繰り返しになりますが、重要なことなのでもう一度。
ラムダ式とは「匿名クラス」の実装を簡潔に実装できるものでしたよね。
つまり、匿名クラスをラムダ式に置き換えることで、簡潔にコードを記述することができるということです。
それでは匿名クラスを理解した上で、
- 匿名クラスを使う場合
- 匿名クラスをラムダ式に置き換えた場合
をそれぞれ見ていきましょう。
まずは匿名クラスを使う場合から。
// インターフェース
public interface Sample {
public void greeting();
}
// 実行クラス
public class Main{
public static void main(String[] args) {
Sample sample = new Sample() {
@Override
public void greeting() {
System.out.println("Hello!!");
}
};
sample.greeting();
}
}
匿名クラス解説中に出した例と、全く同じコードです。
上記コードを、ラムダ式に置き換えると以下のようになります。
// 関数型インターフェース
@FunctionalInterface
public interface Sample {
public void greeting();
}
// 実装クラス
public class Main{
public static void main(String[] args) {
Sample sample = () -> System.out.println("Hello!!");
sample.greeting();
}
}
匿名クラスをラムダ式に置き換えることで、実装からインスタンス化までが、たったの一行で済むようになりました!
※ここではあえて「インターフェース」と「関数型インターフェース」というように分けていますが、どちらも一緒です。詳しくは後ほど解説します。
このまま解説を終えると、
何がどうしてそうなった…。
と考え込んでしまうと思うので、なぜラムダ式を使うことで一行に置き換わるのかを順番に解説していきます。
匿名クラスがラムダ式に置き換わる過程を解説
そもそもラムダ式は、通常のインターフェースを実装することはできません。
ラムダ式は「関数型インターフェース」を実装した、匿名クラスとして扱われます。
関数型インターフェースとは、抽象メソッドを1つだけ持つインターフェースのことです。
// 抽象メソッドが1つだけなので、関数型インターフェースとして成立する
@FunctionalInterface
public interface Sample {
public void greeting();
}
// 抽象メソッドを2つ以上持っているので、関数型インターフェースとして扱えない
public interface Sample {
public void greeting();
public void greeting2();
}
ちなみに、「@FunctionalInterface」とは、関数型インターフェースを明示するためのものです。
このアノテーションを付与することで、関数型インターフェースの条件(抽象メソッドが1つのみ)を満たさない場合、エラーを出すことができます。
ここまでの内容を踏まえた上で、匿名クラスがラムダ式に置き換わる過程を解説していきます。
まずは、匿名クラスを使った素の状態から。
// 関数型インターフェース
@FunctionalInterface
public interface Sample {
public void greeting();
}
// 実行クラス
public class Main{
public static void main(String[] args) {
Sample sample = new Sample() { // 匿名クラス
@Override
public void greeting() {
System.out.println("Hello!!");
}
};
sample.greeting();
}
}
ここから、匿名クラスをラムダ式に一段階置き換えてみます。
// 実行クラス
public class Main{
public static void main(String[] args) {
Sample sample = () -> {
@Override
public void greeting() {
System.out.println("Hello!!");
}
};
sample.greeting();
}
}
4行目が書き換わりました。
元の匿名クラスでは「new Sample( )」としていましたが、ラムダ式で置き換える場合、4行目の書き方で短く記述することができます。
インターフェース名を記述しなくて良い理由は、「Sample sample」の箇所でSample型を扱うことが明示されているためです。
「( )」の部分で引数を受け取り(省略も可能)、処理の実装部分に受け渡すことができます。
「->」この記号はアロー演算子といって、メソッドの引数定義部分と実装部分を区切るためのものです。
ちなみに、この状態ではまだエラーが出てしまいます。
それでは、ここから更にラムダ式に置き換えていきましょう。
// 実装クラス
public class Main{
public static void main(String[] args) {
Sample sample = () -> {
System.out.println("Hello!!");
};
sample.greeting();
}
}
5, 6, 8行目の、メソッドのオーバーライドを明示している部分が無くなりました。
ラムダ式としては完成しているため、もうエラーは出ません。
なぜ、メソッド名を省略して、処理実装の部分だけ簡潔に書くことができているのか?
この疑問への答えとしては、「関数型インターフェースで定義している抽象メソッドが1つ」だからです。
メソッドが1つしかない = わざわざメソッド名を書かなくても、その1つのメソッドを実装するということが分かります。
そのため、ラムダ式に置き換えることで、処理実装の部分だけ簡潔に書くことができる。という訳です。
ラムダ式を一行に置き換えることで、一番始めに紹介したラムダ式の状態になります。
// 実行クラス
public class Main{
public static void main(String[] args) {
Sample sample = () -> System.out.println("Hello!!"); // {}は省略可能なので削除
sample.greeting();
}
}
最後に
ラムダ式とはなんなのか?を再度簡潔にまとめると、
「匿名クラス」の実装を簡潔に実装できるものです。
そのためラムダ式を理解するには、まず匿名クラスをしっかり理解する必要があります。
Javaのラムダ式は、初めて見るとかなりややこしくて、理解しにくい文法の1つだと思います。
1つ1つ紐解いていくことが重要なので、焦らず習得していきましょう。