Java SE 8のラムダ式の基礎──なぜ必要なのか? 従来記法のリファクタリングを通して、その本質を理解する

Oracle Java & Developers編集部
2014-10-06 12:00:00
  • このエントリーをはてなブックマークに追加
Java SE 8では、言語仕様に大きな変更が加わり、新たに「ラムダ式」が導入された。なぜこの機能が必要なのか、コードの書き方はどう変わるのか? 米国オラクルでJava SEのコア・ライブラリ開発チームをリードするスチュアート・マークス氏が解説する。

>> 後編の記事はこちら

米国オラクルJavaプラットフォーム・グループ テクニカル・スタッフ プリンシパル・メンバーのスチュアート・マークス氏
米国オラクルJavaプラットフォーム・グループ テクニカル・スタッフ プリンシパル・メンバーのスチュアート・マークス氏

 2014年3月にリリースされたJava SEの最新版「Java SE 8」における最大の変更点は、言語仕様に大きな変更が加わり、関数型のプログラミング記法である「ラムダ式(Lambda Expressions)」が導入されたことだ。Java SE 8以降のJavaプログラミングでは、このラムダ式をうまく使いこなしていくことが求められる。

 「そのためには、なぜこの機能が追加されたのか、どう使えば効果的なのかを知ることが近道となります」と語るのは、米国オラクルでJava SEの仕様策定をリードするスチュアート・マークス氏だ。ここでは、マークス氏の解説により、従来のJavaプログラミングで生じていた典型的な問題点を示し、それをリファクタリングしていくことで、ラムダ式がどのような効果をもたらすのかを紹介する。

※ 本記事は、2014年5月に東京で開催された「Java Day Tokyo 2014」におけるスチュアート・マークス氏のセッション「Java SE 8 Lambda式の概要」の内容を基に構成しています。ラムダ式やストリームAPIの導入を主導した開発者の視点から、関数型インタフェースやラムダ式の提供に至った背景を詳しく紹介しています。

自動で電話をかける「Robocallシステム」の実装を考える

 今回は、あるサンプル・プログラムの実装を例にとり、ラムダ式の効果を明らかにしていきます。取り上げるサンプル・プログラムは、連絡先のリストの中から特定の条件にマッチする人に自動的に電話をかける「Robocallシステム」です。

 このシステムの実装で初めに行うのは、個人の連絡先情報を保持するクラスPersonの定義です。

【リスト1:個人の連絡先情報を保持するクラスPersonの実装】

class Person { int getAge(); Sex getSex(); PhoneNumber getPhoneNumber(); EmailAddr getEmailAddr(); PostalAddr getPostalAddr(); ... } enum Sex { FEMALE, MALE }

 米国カリフォルニア州では、16歳以上になると自動車の運転免許を取得することができます。そこで第1の条件として、16歳以上の人に電話をかけるようにしましょう。性別は男女どちらでもよいものとします。この処理を、従来のJavaで素直に実装すると、次のようになるでしょう。robocallが指定した番号に電話をかけるメソッドです。

【リスト2:従来のJavaで素直に実装したクラスMyApplication】

class MyApplication { List<Person> list = …; void robocallEligibleDrivers() { for (Person p : list) { if (p.getAge() >= 16) { PhoneNumber num = p.getPhoneNumber(); robocall(num); } } } }

 さて、条件は極力フレキシブルに表現できるようにしたいところです。リスト2のメソッドrobocallEligibleDriversは、年齢をパラメータ化すると次のように書けます。

【リスト3:年齢をパラメータ化したメソッドrobocallMatchingPersons】

void robocallMatchingPersons(int age) { for (Person p : list) { if (p.getAge() >= age) { PhoneNumber num = p.getPhoneNumber(); robocall(num); } } }

 続いて、次の条件を考えてみます。米国では、18歳から25歳までの男性はSelective Service System※1に登録する義務があります。それでは、この条件に当てはまる人にだけ電話をかけたい場合、メソッドrobocallMatchingPersonsはどのようになるでしょうか。この条件だけを考えた場合は、次のように書けるでしょう。

※1 選抜徴兵局の義務兵役サービス(通称:SSS)。国家の緊急時および戦時に軍の増強が必要になった際、米国大統領および米国議会はSSSの登録者に対して徴兵を行うことができる。

【リスト4:「18歳以上25歳以下の男性」に電話をかけるという条件に対応したプログラム】

void robocallSelectiveService() { robocallMatchingPersons(MALE, 18, 25); } void robocallMatchingPersons(Sex sex, int low, int high) { for (Person p : list) { if (p.getSex() == sex && low <= p.getAge() && p.getAge() <= high) { PhoneNumber num = p.getPhoneNumber(); robocall(num); } } }

 しかし、このメソッドでは、先ほどの「16歳以上の男女」という条件をうまく指定することができません。2つの条件をうまく表現できるような共通のメソッドは作れないでしょうか。次の例は、性別の列挙型(enum)に男女どちらでもよいことを表す「DONT_CARE」を追加してみたものです。

【リスト5:性別の列挙型(enum)に「DONT_CARE」を追加】

enum Sex { FEMALE, MALE, DONT_CARE } void robocallMatchingPersons(Sex sex, int low, int high) { for (Person p : list) { if ((sex == DONT_CARE || p.getSex() == sex) && low <= p.getAge() && p.getAge() <= high) { PhoneNumber num = p.getPhoneNumber(); robocall(num); } } }

 このコードは、パラメータと条件の関係が複雑になってしまっており、使う側は頭を悩ませることになるでしょう。このような複雑化はバグを生む要因となります。もっと簡単に使えるコードを考えてみましょう。次の例は、性別の指定としてnullを許容するようにしたものです。

【リスト6:性別の指定にnullを許容したメソッドrobocallMatchingPersons】

void robocallMatchingPersons(Sex sex, int low, int high) { for (Person p : list) { if ((sex == null || p.getSex() == sex) && low <= p.getAge() && p.getAge() <= high) { PhoneNumber num = p.getPhoneNumber(); robocall(num); } } }

 これは実装としては間違っていないかもしれませんが、「性別がnull」とは、果たして性別が不明であることを意味するのか、それとも男女どちらでもよいということなのか区別がつかないため、これも問題があります。

 ここまでにご覧いただいたのは、条件を一般化しようとする過程で仕様が徐々に複雑化してしまう"悪い例"です。それでは、このような複雑化を避けるには、どうしたらよいでしょうか。

  • コメント(3件)
#1 mau.RunDog   2014-11-22 19:16:28
リスト15の
List<Person) list = …;
は、
List<Person> list = …;
の誤りではありませんか?
#2 ケンジ   2015-04-30 01:56:47
他の言語では当たり前にできていたことを、大々的に新機能って言われてもねぇ。
#3 Koさん   2015-04-30 13:29:28
初心者のために lisp に c++ の皮を被せたものの
Java利用者のレベルが上がったので
皮を一枚剥いだ形でしょうね