ラムダ式とストリームAPIでJavaプログラミングはここまでシンプルになる!--Java SE 8に今すぐ移行すべき理由

Oracle Java & Developers編集部
2015-06-17 13:50:00
  • このエントリーをはてなブックマークに追加

「大きく進化したJava SE 8は企業に多くのメリットをもたらす。プロジェクト・マネジャーやアーキテクトも、今すぐ移行に取り掛かるべき」と訴えるJavaエバンジェリストの寺田佳央氏。ラムダ式とストリームAPIを例にとり、Java SE 8によるプログラミングの特徴と利点を語った。

 2014年3月にリリースされた「Java SE 8」の大きな変更点の1つは、新たな言語機能として「ラムダ式(Lambda Expressions)」が導入されたことだ。同機能を使うことで、開発者は従来よりも効率的にプログラムを書けるようになる。また、併せて導入された「ストリームAPI(Stream API)」を使用することで並列処理の実装が容易となり、マルチコア・プロセッサの能力を生かしたハイパフォーマンスなアプリケーションを迅速に作れるようになる。

 日本オラクルのJavaエバンジェリスト 寺田佳央氏は、企業のプロジェクト・マネジャーやアーキテクトに向けて、「ラムダ式をはじめとするJava SE 8の新機能は、使いこなすための学習コストを上回るメリットをもたらす。今すぐにでも移行に向けた取り組みを始めるべき」と訴える。具体的にどのようなメリットがあるのかを寺田氏が解説する。

※本記事は、日本オラクルが2015年2月に開催した「エンタープライズJavaセミナー2015」における寺田氏のセッション「プロマネ、アーキテクトのためのJava SE 8活用法」の内容を基に構成しています。

Java SE 8がこれからの標準。プロジェクト・マネジャー、アーキテクトは早く移行の取り組みを!

日本オラクル シニアJavaエバンジェリストの寺田佳央氏
日本オラクル シニアJavaエバンジェリストの寺田佳央氏

 ご承知のとおり、今日、世の中のあらゆる場所でJavaが使われています。そのJavaが、Java SE 8で大きく進化しました。プロジェクト・マネジャーやアーキテクトの皆さんは、もうプロジェクト内で試されたでしょうか。

 プロジェクト・マネジャーの皆さんが日頃、最も関心をお持ちなのは「プロジェクトを成功に導くこと」でしょう。そのため、プロジェクトで使う技術が最新のものでなかったとしても、プロジェクトが成功しさえすればよいと考えるかもしれません。実績の少ない新技術をプロジェクトで採用するのは不安だという方もいらっしゃるでしょう。

 一方、アーキテクトにとっては、個別のプロジェクトで「正しいアーキテクチャ」をいかに選定するかが大きな関心事となります。必要に応じて最新技術の活用に挑戦する一方で、新技術の習得にかかる教育コストの負担を心配する方もおられるでしょう。

 これらのことを踏まえたうえで、強くお勧めしたいことがあります。皆さん、早くJava SE 8への移行に向けて準備を進めてください。Java SE 8では、言語仕様を含め大きな追加/変更が行われました。それらの機能により、Java SE 8ではシステムの品質や開発作業が大きく効率化され、パフォーマンスも向上しています。これからのJava開発では、これが標準になるのです。

 今後は、Java SE 8の知識がなければ、これからの時代にふさわしいシステムの設計が行えず、コード・レビューをはじめとするプロジェクト管理も難しくなります。世の中はJava SE 8に向けて大きく動き出しました。本サイト読者の皆さんには、その動きに乗り遅れず、できればこのパラダイムシフトをリードしていただきたいのです。

パフォーマンスが出ないのは古いJavaを使っているから。最新のJavaは超速い!

 それでは、最新のJavaには、どのようなメリットがあるのでしょうか。1つ例をご紹介しましょう。

 Java開発に限らず、一般にシステムの開発から運用までのプロセスは「要件定義」、「設計」、「実装」、「テスト」、「運用」といったフェーズで進んでいきます。この中で、設計から実装のフェーズまでは問題なく進んでいたのに、テストのフェーズになってパフォーマンスが出ないなどの問題が生じることがあります。その原因としてはさまざまなことが考えられますが、どのプログラムでも必ず検討するのが、「作成したプログラムは、システムの性能を最大限に引き出せているか」ということでしょう。

 近年はハードウェアの進化により、プロセッサのマルチコア化が一般的となりました。サーバ・マシンのみならず、デスクトップ・マシンでも複数のコアを備えたプロセッサが普及しています。マルチコア・プロセッサの特徴は、複数コアによる並列処理によって高いパフォーマンスが得られる点です。

 システムのパフォーマンスが良くない場合、担当者は書かれたコードに問題がないか見直します。そのとき、「ここは並列処理にすれば、もっと速くなる」と気付いたとしましょう。しかし、そのプログラムが並列処理を想定した作りになっていなければ大量の手戻りが生じることになり、スケジュール内で対応するのが難しいといった結果になってしまいます。こうしたことが、まさにこれから起こりうるのです。

 ここで、1つデモンストレーションをご覧いただきましょう。このデモは、256個のプロセッサ・コアを持つシステム上でシグマ計算を行うJavaプログラムを実行し、各コアの稼働率を表示するというものです。

 まず、この処理をfor文で実装したプログラムを実行した結果は、次のようになります。バーが1本だけ緑色になっていますが、これはコアを1つしか使っていないということを示しています。


※クリックすると拡大画像が見られます

 JavaはJDK 1.0の時代からマルチスレッドに対応しています。そこで、今度はマルチスレッドで書いたプログラムの実行結果をご覧いただきます。


※クリックすると拡大画像が見られます

 いかがでしょうか。「マルチスレッドでプログラミングすれば、すべてのコアを使える」と思った方がいらっしゃるかもしれませんが、そうではないのです。JDK 1.4までで正しくスレッド・プログラムを実装しなかった場合、コアの稼働率はこのようになります。

 Java SE 5以降、JavaにConcurrency Utilitiesが導入され、さらにJava SE 7ではFork/Joinといった機能が導入されたことで、マルチコア・プロセッサの能力を簡単に使いこなせるようになりました。例えば、Concurrency Utilitiesを使った場合、次のように容易にすべてのCPUを利用できるようになります。


※クリックすると拡大画像が見られます

 以上のことからおわかりいただけるように、最新のJavaを利用しているかどうか、適切に実装しているかどうかで、プログラムの性能は圧倒的に変わります。逆に言うと、未だに「Javaは遅い」と言っている方は、古いJavaを使い、古い作法でプログラムを書いている可能性があります。最新のJavaは、正しく使えばとても速いのです。

 さて、このデモからもJavaがいかに進化しているかがご理解いただけたと思いますが、今日の最新版であるJava SE 8は、Javaの歴史上、最も大きく進化したバージョンだと言われています。そして、その進化の最大の目玉とも言えるのがラムダ式の導入です。以降では、ラムダ式により、これからのJava開発がどう変わるのかを、具体的なコードで見ていきましょう。

プログラムの"本質的な処理"の実装に集中するために──ラムダ式が導入された理由

 ラムダ式を使うと、今日のコンピュータが搭載している複数のプロセッサ・コアを使ったマルチスレッド・プログラムを、これまでよりも大幅に簡潔なコードで作ることができます。そのことを、簡単なプログラムを例にとってご説明しましょう。その例とは、「俳優データの中から、年齢が40歳以上の人を抽出し、年齢の若い順に出力する」というプログラムです。このプログラムでは、配列の中に、次のような形式でデータが入っているとします。

図1:配列内のデータの例 図1:配列内のデータの例
※クリックすると拡大画像が見られます

 Java SE 7までの仕様でこのプログラムを書くと、通常は次のようになります。

 ご覧のとおり、「条件一致による抽出」、「抽出したデータのソート」、「ソートしたデータ出力」の各段階で多くのコードを書く必要があります。また、このコード自体にも、多くの課題があります。

 まず、このコードでは、データを抽出したり、ソートの途中経過を保存したりするためだけに変数を定義しています。並列処理を行う場合は適宜、これらの変数の内容を同期する処理が必要になるので当然、それによってパフォーマンスは低下します。

 さらに、プログラムの中でfor文が2回実行されていますが、先ほどのデモ画面でご覧いただいたように、for文は逐次処理として実行されるため、並列処理化が困難です。データの件数が増えれば増えるほど、この部分でパフォーマンスに影響が出るでしょう。

 加えて、このプログラムは可読性の面でも問題があります。このプログラムが全体として何をするものか、一目見ただけで把握するのは難しいのではないでしょうか。このプログラムを作った時点では気にならなくても、半年後や1年後に行うプログラムの保守やコード・レビューの時点では、このことも問題になるかもしれません。

 ここで改めて、このプログラムで実現したい処理が何なのかを確認しておきましょう。やりたいことは、「40歳以上の俳優を抽出する」、「年齢の若い順にソートする」、「結果を出力する」の3つです。全体のコードの中で、これらの"本質的な処理"が書かれている部分は、次に示すようにたった3行しかありません。

図3:図2のプログラムの本質的な処理 図3:図2のプログラムの"本質的な処理"
※クリックすると拡大画像が見られます

 皆さんはこれまで、このわずか3行の処理を実現するために、そのお膳立てをするコードを沢山書いてきました。作りたいプログラムの本質から離れた処理(図中のグレーアウトした部分のコード)の実装に多くの時間を割いてきたのです。

 Java SE 8では、この問題を大きく改善し、皆さんがプログラムの本質に集中してシンプルにコードを書けるよう仕様が改善されています。そのために導入されたのがラムダ式なのです。

 それでは、上のコードをJava SE 8のラムダ式で書き直してみましょう。すると、次のようになります。

図4:図2のプログラムをラムダ式を使って書き直した例 図4:図2のプログラムをラムダ式を使って書き直した例
※クリックすると拡大画像が見られます

 必要なコードはこれだけです。Java SE 8のラムダ式を使うと、「やりたいこと」にかかわる本質的な処理だけを書き、お膳立ての処理をJava SEのライブラリに任せることで、圧倒的にわかりやすく、少ないコードで同じ処理を実現できるのです。

ストリームAPIで、並列処理をシンプルに実装する

 次に、これらの処理を並列化することを考えてみます。コレクション操作に対する並列処理には、Java SE 7で導入された「Fork/Joinフレームワーク」を使うことができます。図2のプログラムのソート処理を、Fork/Joinを使って並列化すると次のようになります。


※クリックすると拡大画像が見られます
図5、6:図2のプログラムのソート処理を行う部分をFork/Joinで並列化した例
※クリックすると拡大画像が見られます

図5、6:図2のプログラムのソート処理を行う部分をFork/Joinで並列化した例

 ソートを行うだけでも、これだけ多くのコードが必要になります。当然、他の部分も並列化しようとすれば、さらに長いコードを書かなければなりません。それに、コードが長くなれなるほどバグが発生する可能性が高まり、可読性も低下します。

 それでは、この並列化処理を、Java SE 8のラムダ式と、並列化処理のために新たに追加されたストリームAPIを使って書くとどうなるでしょうか。これも簡単です。図4のコードで「users.stream()」と書いていた部分を「users.parallelStream()」に置き換えるだけでよいのです。これだけでプログラムの処理が並列化されます。

図7:図4のプログラムをストリームAPIを使って並列化した例 図7:図4のプログラムをストリームAPIを使って並列化した例
※クリックすると拡大画像が見られます

 ラムダ式とストリームAPIを使うことで、コードの量が圧倒的に少なくなることがおわかりいただけたでしょうか。コードの量が減れば、コーディングが楽になるだけでなく、バグが発生する可能性も減りますし、コード・レビューや保守の効率も高まります。

 ここまでのことを踏まえ、私は皆さんに「Java SE 8による開発では、for文を極力使わないようにする」ことをご提案します。なぜなら、すでにおわかりのように、for文を使わずにラムダ式とストリームAPIを使うことで、Java SE 8のメリットを存分に生かしたプログラミングが行えるからです。