知っといてムダにならない、Java SE 8の肝となるラムダ式の基本文法:Java 8はラムダ式でここまで変わる(2)(3/3 ページ)
本連載では、今までJavaの経験はあっても「ラムダ式は、まだ知らない」という人を対象にラムダ式について解説していきます。今回は、基本文法の概要とさまざまな省略の仕方、匿名クラスとラムダ式の共通点と違い、応用的な使い方についてコード例を交えて解説します。
ラムダ式を応用した記述方法
複数の関数型インターフェースを定義する場合
関数型インターフェースの中には、戻り値が特定のクラスやインターフェースのインスタンスを返すものを作成することも可能です。もし、その戻り値も関数型インターフェースの場合、呼び出し元の関数型インターフェースおよびその戻り値の関数型インターフェースも一緒に実装可能です。
例えば、次のような何らかのインターフェースを返すGetInterfaceがあったとします。
@FunctionalInterface public interface GetInterface<T> { T get(); }
このGetInterfaceが関数型インターフェースであるDoSomethingInterfaceを返し、そのDoSomethingInterfaceが「Hello」と標準出力するように実装する場合、次のようになります。
public static void main(String[] args) { GetInterface<DoSomethingInterface> getInterface = ()->()->System.out.println("Hello"); DoSomethingInterface doSomethingInterface = getInterface.get(); doSomethingInterface.doSomething(); }
これを実行すると、次のようになります。
Hello
ここでは、2つの関数型インターフェースの処理が一緒に実装されています。3行目の「()->()->System.out.println("Hello")」がGetInterfaceの実装部分で、そこからさらに「()->System.out.println("Hello")」がDoSomethingInterfaceの実装部分です。
GetInterfaceはラムダ式のままの状態で、DoSomethingInterfaceの実装をラムダ式から匿名クラスに変えると、次のようになります。
GetInterface<DoSomethingInterface> getInterface = () -> { return new DoSomethingInterface() { @Override public void doSomething() { System.out.println("Hello"); } }; }; DoSomethingInterface doSomethingInterface = getInterface.get(); doSomethingInterface.doSomething();
ラムダ式の再帰的な呼び出し
ラムダ式では文法的に正しければ処理の実装で再帰的に自分自身のメソッドを呼び出すことも可能です。
例えば、次のような引数のintを文字列に変換する関数型インターフェースがあったとします。
@FunctionalInterface public interface IntToStringInterface { String convert(int value); }
ここで引数が10より小さい場合は引数を加算して再び自分自身にその値を渡し、10以上の場合は文字列に変換するような処理を実装した場合、次のようになります。8行目の「functionalInterface.convert(++value)」の部分が自分自身の呼び出しです。
public class SampleClass { private IntToStringInterface functionalInterface; private void process(int arg) { functionalInterface = value -> value < 10 ? functionalInterface.convert(++value) : String.valueOf(value); System.out.println("結果=" + functionalInterface.convert(arg)); } public static void main(String[] args) { SampleClass sample = new SampleClass(); // 10より小さい場合 sample.process(0); // 10以上の場合 sample.process(11); } }
これを実行すると下記の結果を得られます。ここでは与えられた引数が0(ゼロ)の場合、その引数が10になるまで加算して再帰的に呼び出され、10になった時点で文字列として返されています。与えられた引数が11の場合はそのまま文字列に変換して返されています。
結果=10 結果=11
ここで行われていることを細かく見ていきます。まず、引数の値「value」が10より小さい場合、valueを加算して再び自分自身を呼び出しています。そして再度「value < 10」の評価を行い、valueの値が10になるまで繰り返されます。
また、ここでfunctionalInterfaceの関数型インターフェースが実装されているので、それ以前にfunctionalInterfaceの関数型インターフェースが定義していても、その処理はここで書き換えられてしまいます。
例えば、次のようにクラス変数での宣言時に定義していた場合、SampleClassのprocessメソッドで関数型インターフェースのfunctionalInterfaceの処理を新しい実装で上書きしているため、クラス変数宣言時に定義していた処理は実行されることはありません。
次回は、ラムダ式に関連するJava 8の新しいAPIを紹介
今回はJavaのラムダ式の読み書きができるように基本的な記述方法を見てきました。しかし後半で紹介した応用した記述のように複雑になった記述が出てきた場合、何をしているのかすぐに分かりづらい実装が出てくることが想像できます。
そして、そのことはバグが見つけづらい実装や書いた本人しかすぐには解読できないような実装が今後出てくる原因になるかと思われます。そういうことを防ぐためにもラムダ式の実装ルールをうまく作っておかないと後々管理しづらくなることが予想できます。
Javaのラムダ式が正式にリリースされた今後は、ラムダ式のメリットを生かしつつ問題が起きにくい実装方法を考えていくのが、これからの課題になっていくのかもしれません。
また関数型プログラミングとは本来は引数の値が毎回同じなら結果も同じものを返し外部への影響を及ぼさないことを原則としています。関数型インターフェースをこの関数型プログラミングという点で見ると今回説明したようなクラスの変数やメソッドを扱うのは外部への影響を与えるため、この原則から外れてしまいます。
しかし、例えばGUIの実装の際に多くのListenerを関数型インターフェースとして扱うことができるので、ラムダ式を使ってソースを簡潔に実装することはあるかと思われます。そのため関数型というだけで、この原則に縛ることは実際には難しいかと思います。そこら辺を含めJavaの世界が今後どのようになるのか興味深い点でもあります。
次回からはラムダ式に関係がある、「Stream」などJava 8の新しいAPIを見ていきます。ご期待ください。
著者プロフィール
長谷川 智之(はせがわ ともゆき)
株式会社ビーブレイクシステムズ開発部所属。
社内サークル執筆チーム在籍。
主な執筆。
@IT連載『Javaの常識を変えるPlay framework入門』
日経ソフトウェア連載『コツコツ学ぶAndroidネイティブアプリ開発教室』
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- JavaOne Tokyo 2012まとめレポート(後編):ラムダ式、JAR脱獄、JavaScript/■■Node.jsに接近するJDK 8、そして9へ
JDK 8の新機能のうち、Lambda、Jigsaw、Nashorhについて解説した講演を詳細にレポートする。そしてJava SE 9はどうなる? - スケーラブルで関数型でオブジェクト指向なScala入門(4):基本的なパターンマッチとScalaで重要な“関数”
match構文、関数の定義と呼び出し、高階関数、プレイスホルダ構文、部分適用、クロージャなどを紹介します - 「Javaに並列処理と関数型言語の要素を」、ティム・ブレイ氏
- いよいよ始まるRuby 1.9への移行:関数型っぽくなったRuby 1.9
- WebSocketやREST APIのサポート強化:Java 8&Java EE 7に対応した「Spring Framework 4.0」正式版リリース
米Pivotalは2013年12月12日、オープンソースのJavaアプリケーションフレームワーク「Spring Framework 4.0」の正式版をリリースした。 - JDK 8、TLS 1.2がデフォルトに
Javaの通信暗号化もTSL 1.2に。基本的に後方互換性は維持するが、一部影響がある場合もあるという。 - .NET開発『虎の巻』:C#ラムダ式 基礎文法最速マスター
ラムダ式(C#)の基礎文法を、短い説明と簡単なコードで簡潔にまとめる。「ラムダ式、どう書くんだっけ?」という場合の簡易リファレンスとして活用できる - .NET開発『虎の巻』:VBラムダ式 基礎文法最速マスター
今度はVB。ラムダ式の基礎文法を、短い説明と簡単なコードでまとめる。「ラムダ式、どう書くんだっけ?」という場合の簡易リファレンスとして活用できる - 特集:C#開発者のためのF#入門(前編):F#で初めての関数型プログラミング
アルゴリズム実装などに威力を発揮する、関数型プログラミングの基礎やF#言語の特徴を解説。C#開発者なら、ここから始めよう! - Gaucheでメタプログラミング(1):ちょっと変わったLisp入門
Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう