はじめまして。梶原です。
2014年3月のリリースからもう1年が過ぎようとしています。Java 8、皆さんはもう実際にコードに触れてみましたか?
私はJava 一色だった仕事を離れて6年になります。Java 歴はそれなりに長いのですが、最後の数年はコードをがっつり書く業務ではなくなっていました。最近ではパっと見で読めない Java コードを目にする機会が多くなり、リハビリの必要性を痛感しています。そして、気になるキーワードが1つあります。Java 8 に新たに搭載された「ラムダ式」です。
「知らない」では MOTTAINAI
人は知らない人やモノへの評価は自然と否定的になるのだといいます。。
「新機能は何となく気になってはいるんだけど、知っている記法でコーディングしても動くし、もちろんコンパイルも通る。普段の仕事だって忙しいから時間をかけて研究するなんて無理。どんな書き方でも動けばいいんでしょ? そのほうが仕事も速いし。」
でも。考えてみてください。
「どんな書き方でも」美しく Java の特性を曲げず邪魔せず本領を発揮させるコードが書ける人、というのは洗練されたセンスを持ったごく一部の人だけです。
「Java 8 、勉強してみようかな」という私には、Java を開発する神様たちが「こうすれば安全なコードが書けるよ」とヒントをくれているのを使わない手はないのです。少しずつ理解して自分のコードに取り入れていく。それを目指して一歩ずつ進んでいこうと思います。
ラムダ式には押さえておきたい様々なトピックがあるようです(少し決心が挫けそうです)。
今回は新機能のほんのさわりの部分、コードに何をさせるのか「発想の転換」について確認していこうと思います。
発想の転換 -従来の記法との比較-
まずは、2005年の記事「Java言語機能(JDK5.0(Tiger)新機能) 2章 拡張for文」を参考にして、従来の書き方を思い出してみます。(※一部コードを変更しています。)
まずは地名を格納したリストを作成します。
1 2 3 4 |
List<String> cities = new ArrayList<String>(); cities.add("京都"); cities.add("大阪"); cities.add("神戸"); |
お題目:この要素を1つずつ取り出して標準出力に出力
世紀末~21世紀初頭に Java を始めた人なら、こんなコーディングを思い浮かべるはずです。
1 2 3 |
for(int i = 0; i < cities.size(); i++) { System.out.println(cities.get(i)); } |
記事中の古い記法の例もこんな感じです。
1 2 3 |
for(Iterator<String> iterator=cities.iterator(); iterator.hasNext(); ){ System.out.println(iterator.next()); } |
記事を参考にすると、もう少しイマ風に書けます。
1 2 3 |
for(String city : cities){ System.out.println(city); } |
なぜこのように書けるのか、気になった場合は記事の「2.2. java.lang.Iterable」を参照してください。
「どのように実現するか」から「何を実現させるのか」への転換
さて、ここからが新機能です。
Java 8 では Iterable インタフェースに forEach(Consumer<T> action) メソッドが追加されました。どうなったのかを見てみます。
1 2 3 4 5 |
cities.forEach(new Consumer<String>() { public void accept(final String city) { System.out.println(city); } }); |
forEach() メソッドは java.util.function.Consumer 型を引数にとります。Consumer のインスタンスは accept() メソッドによって与えられるものをサクサク消費します。
ここで押さえておきたい大切なポイントは、命令型である外部イテレータから関数型の内部イテレータへと変化している点です。
「要素を1つずつ取り出したい、そのためにはイテレートをどのように行うか、それから何をするのか」
から、
「要素を取り出す、それをこうする」
にしようとしています。
たしかに、やりたいことは各要素の標準出力であってイテレートを頑張ることではないですよね。私には「おおー」と目からウロコでした。
今回の記事で書きたいことはここまでだったのですが、例に挙げたコードではどうしても許せない感じがするのでもう少し続けます。どんどんコード量が削減されていきます。こんなに変わるのか!?と引いてしまわずに、楽しんで眺めてください。
それ、書かなくてもいいんです
ここで(やっと)ラムダ式が登場します。
1 |
cities.forEach((final String city) -> System.out.println(city)); |
コードが1行になりました。これが「ラムダ式」です。
forEach() 関数に (final String city) -> System.out.println(city) という関数を渡しています。この渡される関数は forEach() が呼び出される時点で生成されます。この関数はパラメータ (final String city) に続いてアロー(->)が記述され、関数本体の System.out.println(city) が続きます。
1行にはなりましたが、書かなくていいところがあります。
1 |
cities.forEach((city) -> System.out.println(city)); |
パラメータの型情報も抜いてしまいました。
ここまでで注目する点は、パラメータにも関数の戻り値にも型を記載する必要はない、ということ。
パラメータの型は Java によって推論されて、戻り値の型も暗黙的です。
まだ書かなくていいところがあります。
1 |
cities.forEach(city -> System.out.println(city)); |
パラメータを1つしか持たないラムダ式においてJava が型を推論できる場合は、括弧を書かなくても良いとされています。
ここまででも随分コードの量が減りましたが、最後に Java 8 の新機能「メソッド参照」を利用すると更に書かなくていいところがあります。
1 |
cities.forEach(System.out::println); |
もう世紀末時代の面影もありません。。。
まとめ
- 新機能(ORACLE:JDK 8の新機能)
この記事で触れたもの - ラムダ式というものがある
- ラムダ式 + メソッド参照でコードがシンプルになる
- 次のステップに進むためのキーワード
- 関数インタフェースを受ける場所でラムダ式が使用可能
- 関数インタフェースにどのような種類があるのか
最後に
私の教科書です。特にコードの削減部分については「Javaによる関数型プログラミング」が大いに参考になりました。読み物としても楽しい本です。
O'Reilly Japan - Javaによる関数型プログラミング(Venkat Subramaniam 著)(株式会社プログラミングシステム社 翻訳)オライリージャパン
Javaプログラマーなら習得しておきたい Java SE 8 実践プログラミング(Cay S. Horstmann 著)(柴田 芳樹 翻訳)インプレス
Java逆引きレシピ(竹添 直樹,高橋 和也,織田 翔,島本 多可子 著)翔泳社