Java ラムダ式に関する予備研究
前書き
この記事は、JavaOne 2016 での Trisha Gee の基調講演「Java 8 へのリファクタリング」に触発されました。
Java 8 がリリースされて 2 年以上経ちますが、多くの人がまだ JDK7 を使用しています。企業にとって、テクノロジーに慎重になることは必ずしも悪いことではありませんが、個人の学習においては、新しいテクノロジーを学ばないと、テクノロジーから見放されてしまう可能性が高くなります。 Java 8 の重要な変更点は、Lambda 式の導入です。Lambda 式が何なのかは知りませんが、それでも素晴らしいと思います。心配しないでください。言語レベルでは、ラムダ式は単なる新しい構文であり、Java は関数型プログラミングへの扉を開きます。
なぜラムダ式が必要なのでしょうか?
ラムダ式とは何か、関数型プログラミングとは何かについて心配する必要はありません。まず、Java 8 の新しい構文機能によってもたらされる利便性を見てみましょう。それらは覚えていると思います。
ラムダ式がある前に、新しいスレッドを作成するには、次のように書く必要があります:
new Thread(new Runnable(){ @Override public void run(){ System.out.println("Thread run()"); } }).start();
ラムダ式がある後、次のように書くことができます:
new Thread( () -> System.out.println("Thread run()") ).start();
ご覧のとおり、以前は役に立たなかったテンプレートコードが消えてしまった!上で示したように、ラムダ式の一般的な使用法は、(一部の) 匿名内部クラスを置き換えることですが、ラムダ式の役割はこれに限定されません。
ラムダ式の原理
ラムダ式を初めて使用する場合は、それが魔法だと思うかもしれません。クラスやメソッドの名前を宣言せずに関数を直接定義できます。しかし実際には、これはコンパイラが提供するちょっとしたトリックにすぎず、その背後にある原理を理解するのは難しくありません。以下は、ラムダ式の可能な記述形式のいくつかです:
Runnable run = () -> System.out.println("Hello World");// 1ActionListener listener = event -> System.out.println("button clicked");// 2Runnable multiLine = () -> {// 3 System.out.println("Hello "); System.out.println("World"); }; BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5
上記の例からわかります:
ラムダ式には型があり、代入演算の左側が型です。ラムダ式の型は、実際には対応するインターフェイスの型です。
ラムダ式には複数行のコードを含めることができ、関数本体を記述する場合と同様に、中括弧を使用してコード ブロックを囲む必要があります。
リスト 2 とリスト 5 にあるように、ほとんどの場合、型はラムダ式のパラメーター リストから省略できます。これは javac の型導出メカニズムによるもので、コンパイラはコンテキストに基づいて型情報を推定できます。
実際、各 Lambda 式は、関数インターフェイス (Functional Interface) を実装する元の匿名内部クラスの略称です。いわゆる関数型インターフェイスとは、 @FunctionalInterface アノテーションが追加され、内部にインターフェイス関数が 1 つだけ含まれているインターフェイスを指します。 Java は厳密に型指定された言語です。明示的に指定されているかどうかに関係なく、各変数とオブジェクトには明確な型が必要です。明示的に指定されていない場合、コンパイラーは型を決定しようとします。ラムダ式の型は、対応する関数インターフェイスの型です。
ラムダ式とストリーム
ラムダ式のもう 1 つの重要な用途は、ストリームで使用することです。ストリームは、順次および並列の集計操作をサポートする一連の要素です。ストリームは、これらの要素に対するさまざまな操作をサポートする一連の要素であり、これらの操作はラムダ式を通じて指定されます。イテレータがコンテナのビューであるのと同じように、ストリームは Java コレクションのビューと考えることができます (ただし、ストリームはコンテナの内容を変更しません)。次の例は、Stream の一般的な使用法を示しています。
例 1
文字列リストから数字で始まる文字列を選択して出力する必要があるとします。Java 7 より前では、次のように記述する必要がありました:
List<String> list = Arrays.asList("1one", "two", "three", "4four");for(String str : list){ if(Character.isDigit(str.charAt(0))){ System.out.println(str); } }
Java 8 では、次のように記述できます。
List<String> list = Arrays.asList("1one", "two", "three", "4four"); list.stream()// 1.得到容器的Steam .filter(str -> Character.isDigit(str.charAt(0)))// 2.选出以数字开头的字符串 .forEach(str -> System.out.println(str));// 3.输出字符串
上記のコードは、まず 1. List.stream() メソッドを呼び出してコンテナの Stream を取得し、 2. 次に filter() メソッドを呼び出して数字で始まる文字列を除外します。 forEach() メソッドを使用して結果を出力します。
Stream の使用には 2 つの明らかな利点があります:
テンプレート コードが削減され、必要な操作の指定にラムダ式のみが使用され、コード セマンティクスがより明確で読みやすくなります。
外部反復を Stream の内部反復に変更します。これにより、JVM 自体の反復プロセスの最適化が容易になります (たとえば、並列で反復できます)。
例 2
文字列のリストから数字で始まらないすべての文字列を選択し、それらを大文字に変換し、結果を新しいコレクションに入れる必要があるとします。 Java 8 で書かれたコードは次のとおりです:
List<String> list = Arrays.asList("1one", "two", "three", "4four"); Set<String> newList = list.stream()// 1.得到容器的Stream .filter(str -> !Character.isDigit(str.charAt(0)))// 2.选出不以数字开头的字符串 .map(String::toUpperCase)// 3.转换成大写形式 .collect(Collectors.toSet());// 4.生成结果集
上述代码首先1. 调用List.stream()方法得到容器的Stream,2. 然后调用filter()方法选出不以数字开头的字符串,3. 之后调用map()方法将字符串转换成大写形式,4. 最后调用collect()方法将结果转换成Set。这个例子还向我们展示了方法引用(method references,代码中标号3处)以及收集器(Collector,代码中标号4处)的用法,这里不再展开说明。
通过这个例子我们看到了Stream链式操作,即多个操作可以连成一串。不用担心这会导致对容器的多次迭代,因为不是每个Stream的操作都会立即执行。Stream的操作分成两类,一类是中间操作(intermediate operations),另一类是结束操作(terminal operation),只有结束操作才会导致真正的代码执行,中间操作只会做一些标记,表示需要对Stream进行某种操作。这意味着可以在Stream上通过关联多种操作,但最终只需要一次迭代。如果你熟悉Spark RDD,对此应该并不陌生。
结语
Java 8引入Lambda表达式,从此打开了函数式编程的大门。如果你之前不了解函数式编程,不必纠结于这个概念。编程过程中简洁明了的书写形式以及强大的Stream API会让你很快熟悉Lambda表达式的。
本文只对Java Lambda表达式的基本介绍,希望能够激发读者对Java函数式编程的兴趣。如果本文能够让你觉得Lambda表达式很好玩,函数式编程很有趣,并产生了进一步学习的欲望,那就再好不过了。文末参考文献中列出了一些有用的资源。
参考文献
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
http://www.slideshare.net/trishagee/refactoring-to-java-8-devoxx-uk
《Java 8函数式编程 [英]沃伯顿》
https://www.oracle.com/javaone/speakers.html#gee