Java8からラムダ式と呼ばれる記述方式が盛り込まれました。
ラムダ式を使うと何が便利なのかと言うと、メソッドを変数と同様に扱うことができ、記述を簡略化できるのです。
つまり、記述量が減ってシンプルな表記にできることがメリットです。
この記事では、ラムダ式について以下の内容で解説してきます。
・ラムダ式とは
・ラムダ式のメリット
・ラムダ式の使い方
・関数型interfaceの実装をラムダ式に置き換え
・Stream APIの使い方
・ラムダ式の注意点
今回はラムダ式について、使い方をわかりやすく解説します!
なお、Javaの記事については、こちらにまとめています。
ラムダ式とは
ラムダ式というのは、あまり馴染みはないかもしれませんがF#、Scala、Schemeといった関数型言語と呼ばれるプログラミング言語における用語です。
かんたんに言うと、「メソッドを変数と同様に扱う記述様式」になります。
Java8から追加され、使用することができるようになりました。
ラムダ式のメリット
ラムダ式を使用することによるメリットは主に2つあります。
・関数型インターフェースを実装するのためのソースを手短に書ける
・「Stream API」の引数としての関数型インターフェースを記述するのに適している
ちなみに、関数型インターフェースとは抽象メソッドを1つ持つインターフェースのことです。
また、「Stream API」とは配列やList、Collectionなどの要素の集合に対して、要素の直列処理や並列処理、要素の比較並び替え、一致判定などの処理を実行するメソッド群のことです。
この2つのメリットについて、サンプルコードを使って詳しく解説していきます。
ラムダ式の使い方一覧
ラムダ式の基本的な構文は下記のようになります。
インターフェース名 オブジェクト名 = (引数1, 引数2, ・・・) -> {return 処理内容};
なお、引数の型は記述する必要はありません。
引数の型はコンパイラーによって類推されるので、記述する必要がないのがラムダ式の記述の特徴の一つでもあります。
だからと言って、従来の記述様式のように型を記述してもコンパイルエラーになることもありません。
ラムダ式の引数が1つの場合
ラムダ式の引数が1つの場合はより手短に記述することができます。
インターフェース名 オブジェクト名 = 引数 -> 処理;
引数が1つの場合は処理結果を返す「return」句や引数群を囲む「()」、処理の記述を囲む「{}」を記述する必要がありません。
メソッド参照一覧
メソッド参照とは、メソッドの引数としてメソッドを参照できる仕組みのことです。
呼び出したいメソッド名の直前に「::」句を、さらにその前にはクラス名を記述し、メソッドの引数部分の「()」は省略する型で以下のように記述します。
クラス名 :: メソッド名
メソッド参照については下記のように表にまとめました。
構文 | ラムダ式例 | 従来記述例 | |
---|---|---|---|
クラスメソッド | クラス名::メソッド名 | String::toString | String.toString(引数) |
インスタンスメソッド | オブジェクト名::メソッド名 | System.out::println | System.out.println(引数) |
コンストラクタ | クラス名::new | ArrayList::new | new ArrayList() |
メソッド参照もjava8から追加された機能の一つです。
ラムダ式と同じように、手短に記述することができてラムダ式と一緒に使われることも多いです。
メソッド参照は、staticなメソッドを記述する場合に使われます。
なお、メソッド参照と同じように、クラスのコンストラクタを参照する仕組みのことをコンストラクタ参照といいます。
メソッド参照の詳しい解説についてはこちらも参考にしてくださいね。
関数型interfaceの実装をラムダ式に置き換え
関数型インターフェースとは抽象メソッドを1つだけ持つインターフェースのことです。
抽象メソッドを実装する際に処理内容をラムダ式で記述することができます。
匿名クラスでの記述(従来例)
まずは、これまでの匿名クラスでの記述についてサンプルコードで確認していきましょう。
// インターフェース interface InterfaceTest{ // 抽象メソッド public String method(String name, int n); } public class Main { public static void main(String[] args) { // 匿名クラスの場合 InterfaceTest it = new InterfaceTest() { // オーバーライド public String method(String name, int n) { return "Hello " + name + n + "!"; } }; System.out.println(it.method("Java", 8)); } }
実行結果:
Hello Java8!
このサンプルコードではインターフェースを定義し、実際に使用する際に抽象メソッドを実装しています。
ラムダ式での記述
それではラムダ式の記述についてサンプルコードで確認していきましょう。
// インターフェース interface InterfaceTest{ // 抽象メソッド public String method(String name, int n); } public class Main { public static void main(String[] args) { // ラムダ式の場合 InterfaceTest it = (name, n) -> { return "Hello " + name + n + "!"; }; System.out.println(it.method("Java", 8)); } }
実行結果:
Hello Java8!
このサンプルコードのように、匿名クラスをラムダ式で手短に書き換えることができます。
ラムダ式で引数のnameやnにStringやintなどの型指定を記述する必要がないのも、手短に書くことができる要因の一つです。
「Stream API」の引数にラムダ式を使う方法
まず「Stream API」についてですが、これもJava8から新たに言語拡張された機能の一つです。
「Stream API」とは配列やList、Collectionなどの要素の集合に対して処理を行う場合に便利なAPIです。
要素の直列処理や並列処理、要素の比較並び替え、一致判定などの処理を実行するメソッドが用意されています。
下記のように記述して使用します。
配列やList、Collectionなどのオブジェクト名.stream().メソッド名((引数) -> {処理})
メソッドの引数にはラムダ式の記述様式を使用します。
ここでは、「filter」、「sorted」、「map」などのメソッドを使って要素の処理を手短に記述する例をご紹介します。
forEachの使い方
まずはforEachメソッドを使ってforループ文を手短に書く方法について説明します。
サンプルコードで確認していきましょう。
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { Integer[] num = {1, 2, 3, 4, 5}; List<Integer> l = Arrays.asList(num); // ラムダ式の記述 l.forEach(System.out::println); /* // for文での記述 for(int i = 0; i < l.size(); i++) { System.out.println(l.get(i)); } */ } }
実行結果:
1 2 3 4 5
このサンプルコードでは、forEachメソッドとメソッド参照(System.out::println)を使って手短に記述することができています。
なお、for文で記述する場合の例を比較としてコメントアウト部に記しています。
filterの使い方
filterメソッドを使って条件式を満たす場合の処理の記述方法について、サンプルコードで確認していきましょう。
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { Integer[] num = {1, 2, 3, 4, 5}; List<Integer> l = Arrays.asList(num); // ラムダ式の記述:3未満を抽出 l.stream().filter(x -> x < 3).forEach(System.out::println); } }
実行結果:
1 2
このサンプルコードではfilterメソッドを使って引数xの値が3未満という条件を満たす場合のみ、xの値を出力表示しています。
filterの引数の処理はラムダ式で記述しています。
また、先ほどのサンプルコードでご説明をしたforEachメソッドとメソッド参照(System.out::println)を使って記述することで、1行で手短に記述することができています。
sortedの使い方
sortedメソッドを使ってソート処理を行う記述の方法について、サンプルコードで確認していきましょう。
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { Integer[] num = {1, 3, 5, 4, 2}; List<Integer> l = Arrays.asList(num); // ラムダ式の記述:降順にソート l.stream().sorted((x, y) -> y - x).forEach(System.out::println); } }
実行結果:
5 4 3 2 1
このサンプルコードでは、sortedメソッドを使って降順にソートする例を記述しています。
sortedの引数の処理をラムダ式で記述し、またforEachメソッドとメソッド参照を使うことで1行で手短に記述することができています。
mapの使い方
mapメソッドを使って引数を四則演算する方法について、サンプルコードで確認していきましょう。
import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { Integer[] num = {1, 2, 3, 4, 5}; List<Integer> l = Arrays.asList(num); // ラムダ式の記述:四則演算 l.stream().map(x -> x * 10 - 5).forEach(System.out::println); } }
実行結果:
5 15 25 35 45
このサンプルコードでは、mapメソッドを使って引数を四則演算する例を記述しています。
これまでと同じように、1行で手短に記述することができています。
List⇔Mapの変換でStreamAPIを使う方法
StreamAPIを使って、ListとMapの変換を行うこともできます。
ListとMapの変換にStreamAPIを使う方法についてはこちらの記事で詳しく解説しているので、ぜひ確認してください。
ラムダ式の注意点
ラムダ式を使う場合に、変数を参照する場合は注意が必要です。
ラムダ式は実装したクラスを持たない匿名クラスと同じですので、匿名クラスのスコープ外で定義された変数を使用する場合、その変数はfinalもしくは実質的finalでなければなりません。
値が変更されると矛盾が生じてしまうからです。
下記の例のようにラムダ式の内部では値を参照することはできますが、この値をラムダ式の内部および外部で変更する記述は、いずれもコンパイルエラーが発生しますので注意が必要です。
interface InterfaceTest{ // 抽象メソッド public String method(String name); } public class Main { public static void main(String[] args) { // finalを省略できるが、実質的にはfinal int n = 8; // ラムダ式の記述 (引数) -> {処理}; InterfaceTest it = name -> {// n = 10; // コンパイルエラー return "Hello " + name + n + "!"; //参照の場合はOK }; System.out.println(it.method("Java")); // n = 10; // コンパイルエラー } }
実行結果:
Hello Java8!
Java8の新機能まとめ
Java8ではラムダ式やStreamAPIのほかにも様々な機能が追加されました。
Java8の新機能についてはこちらの記事で詳しく解説しているので、ぜひ確認してください。
まとめ
ここでは、Java8から追加になったラムダ式について使い方や使う上での注意点について説明しました。
ラムダ式の記述様式を使うことで、手短にコードを書くことができたり、配列などの要素の集合を処理する場合に「Stream API」などが使えて便利なことがおわかり頂けたかと思います。
今後ラムダ式で記述されたコードを見かけることも多くなると思いますので、慣れて使いこなせることができるようにこの記事を何度も参考にして下さいね!