japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2009年7月4日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー2008年6月3日 10:00

「Google Collections Library」でJavaのコレクションを補完する

海外海外internet.com発の記事
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!
  • この記事をokyuuへインポート

はじめに

 何年も前のことですが、私が初めてJavaに興味を覚えた理由の1つは、Javaプラットフォームに標準でコレクションライブラリが組み込まれていたからでした。当時、C++の世界ではまだSTL(Standard Template Library)が定着しておらず、開発者たちは適当なコレクションライブラリを購入して利用するか(Rogue Waveが流行っていました)、自分の手でライブラリを書くしかありませんでした。正確な数は忘れましたが、私自身も、さまざまな目的でさまざまな種類のプリミティブやオブジェクトの連結リストを実装しました。さらに、もっと複雑なコレクションや平衡2分探索木、ハッシュテーブルなども自分で実装しました。そのようにしてソフトウェア工学の原理を絶えず意識することは決して無駄なことでありませんでしたが、生産性を考えるとそうとばかりも言えませんでした。

 しかし、Javaによって事態はまったく変わりました。バージョン1.0と1.1のコレクションクラスでも大きな改善が見られましたが、Java 1.2で導入されたJava Collections Frameworkは生産性を飛躍的に高めるものでした。以来、標準コレクションは拡張と改良を幾度となく繰り返し、Java 5で新たにジェネリックス(Generics)が導入されたことを受けて、(少なくともコンパイル時には)型検査をしたいと考える人々に好んで使われるようになりました。Doug Leaの並列処理コレクションの導入も歓迎されました。これがJava 5以降のJava.util.concurrentに組み込まれたことで、並列処理システムに適したQueueやConcurrentMapのようなコレクションを利用できるようになりました。

 それにもかかわらず標準コレクションには欠点があります。アイテムがたびたび再実装され、ときにはとても最適とは言えないやり方が使われることもあります。また弱点もあります。ジェネリックス(少なくともJavaにおけるその実装)の費用対効果は目下議論されているところですが、好きか嫌いかは別としてジェネリックスが非常に冗長なものであることは間違いありません。たとえば、2003年当時はコレクションが次のようなスタイルで書かれていました。

Map mapOfLists = new HashMap();
 ところが現在は、何かをリストにマップするとき、次のようなスタイルが使われます。

Map<String, List<String>> mapOfLists =
   new HashMap<String, List<String>>();
 これは必ずしも公正な比較ではありません。2番目の定義の方がコンパイル用の情報を多く含んでおり、キーにはString型のみが用いられること、そしてHashMapの値にはString型のListが用いられることをJavaコンパイラに伝えているからです。とはいえ、お気づきのように同じ情報が幾度となく繰り返され、その定義やさらに初期化の際にも型シグニチャが繰り返されるなど、あまり美しいとは言えません。

 Google Collections Libraryは、GoogleがJavaコミュニティ向けに提供する新たなオープンソースライブラリです。Javaの現在のコレクションが抱える扱いにくさを漸進的に改善することと、独自のコレクションや機能を新たに付け加えることを目指しています。しかし同じ道を進むのはこれだけでなく、当然Apache Commons Collectionsとの比較も必要でしょう。コレクション増強ライブラリとしてどれを選ぶかは概して趣味の問題です。本稿では、Google Collections Libraryを取り上げることにします。私はこのライブラリを(内部的な形ながら)Googleのいくつかのプロジェクトで1年以上使ってみて、かなりよくできていると感じたからです。感触的にはJava Collections Frameworkが自然に発展したものと見ることができ、その高い信頼性と能力、そして何もかもが開示されている点を評価すると、今後のプロジェクトにこれなしで取り組むことなど到底考えられません。幸い、現在オープンソースプロジェクトとして提供されていることからすれば、そのような心配は無用のようです。

Google Collections Libraryを使う理由

 言うまでもなく、以下に挙げる理由は主観的なものです。それでも、Java Collections Frameworkのコレクション増強ライブラリとしてGoogle Collections Libraryを採用することには、それ相応の理由があります。

  1. 今すぐ使える: 現在のバージョンは0.5 alphaですが、これはGoogle社外での使われ方を見て今後ライブラリを改良するためにAPIが変更される可能性がある、という意味です。このバージョン番号とステータスを文字通り受け取って、まだ多くのバグがあるとか、今使うのは早計であるとか判断するのは正しくありません。このライブラリのコードはGoogle社の多くの大規模なプロジェクトで実際に一定期間テストされたものであり、きわどい症例はほぼ出尽くしたと見てよいでしょう。テストカバレッジも85%に達しています。無論、APIは今後まだ変更される可能性があり、それを懸念する気持ちも理解できますが、実際に変更があったとしても小さなもので、簡単に対処できると思われます(保証はできませんが)。それでもやはり不安という方は、ライブラリのステータスに目を光らせ、APIが安定するまで待ってください。
  2. 一貫性がある: 実際に使ってみて、このライブラリの新しいコレクションと、新たに加わった使い勝手のよい機能は、どちらもJava Collections Frameworkの自然な発展形であるように感じられます。偶然の産物ではありません。Javaのコレクションと動作レベルで一貫性を持たせるために多くの労力が投入された結果であり、しかもSunにおける実装の現場でJavaのコレクションを実際に扱った技術者(たとえば、Josh Bloch)が、その作業を監督しているという事実を見落としてはいけません。特に、ジェネリックスはJava Collections Frameworkと完全に同じ方法で扱われています。
  3. 大きさが手頃: 新たな機能を実装したjarファイルのサイズは約350 KBで、たいていのプロジェクトで問題とされるような値ではありません。
  4. ドキュメントが充実: このライブラリのJavadocはどこから見ても完璧であり、この種のライブラリとしては特に優れています。
  5. 高性能: これらのコレクションは、性能を重視するGoogle社のプロジェクトでそのまま利用されており、最大限の性能を引き出すために多くの労力が投入されてきました。
  6. 新機能: コレクションを多用するシステムを意識して、使い勝手を考慮した改良や実用的なアイデアが実装されています。たとえば、コレクションの出力結果をフィルタリングする機能や、制約を適用する機能があります。
 公平を期するため、このライブラリを使うことで生じるかもしれない問題も指摘しておきます。

  1. このライブラリの補助クリエータ(convenience creators)は、Java 7に向けて提案されている型推論と折り合いがよくありません。そのため、このライブラリを利用した場合、将来的に初期化コードの一部を書き換えなければならなくなるかもしれません。あるいは、2つの標準を抱えたまま進む道を選ばざるを得なくなるかもしれません。
  2. これらのコレクションの一部、あるいはそこに含まれるアイデアがJava 7またはJava 8の開発タイムフレームの中でJSRとして1つか2つ提案されることはあるかもしれませんが、多くはそうならないでしょう。つまり、これらのコレクションを今利用するということは、将来それらを標準規格とするための労力が発生するかもしれないことを意味します。
  3. 既に述べたように、APIはまだ変更される可能性があります。

入手方法

 Google Collections Libraryは、http://code.google.com/p/google-collections/からダウンロードできます。本稿の執筆時点のバージョンは0.5です。実際、現時点でAPIの安定性について保証が得られていないので、APIに変更が生じる可能性があり、それによって本稿のサンプルが実態にそぐわなくなることも考えられます。しかし、生じる差異はわずかで、うまくすれば簡単に修正できるものと思われます。

 このライブラリをプロジェクトで実際に利用する手順は簡単で、ダウンロードしたアーカイブを解凍して、そこに含まれるjarファイルをインクルードするだけです。Javadocとソースファイルのアーカイブも提供されており、たいていの統合開発環境(IDE)では、ライブラリを定義するとき、それらがjarファイルに結び付けられます。これでライブラリをより簡単に使えるようになり、またプロジェクトのデバッグも容易になります。

簡単なサンプル

 まずは簡単な機能をいくつか見ていきましょう。簡単とは言っても、どれも私のお気に入りの機能です。最初に紹介するのは、「補助クリエータ」(Convenience Creators)です。

 前述のジェネリックスを用いた、何かをリストにマップする例をもう一度見てみましょう。

Map<String, List<String>> mapOfLists =
   new HashMap<String, List<String>>();
 これが多くの開発者にとって当たり前のスタイルなのではないでしょうか。実際、私はこれよりずっと複雑なコレクションの宣言や初期化の型シグニチャを書いてきました。必ずと言ってよいほど、その左辺と右辺の両方でこのシグニチャが繰り返されていました。

 Java 7に向けて提案されている型推論を適用すると、上の例は次のように書き換えられます。

Map<String, List<String>> mapOfLists =
   new HashMap<>();
 確かに改善が見られます。こう書いても、コンパイラは常識的な推論に基づきnew HashMapのジェネリック(総称的)な型シグニチャを組み立てるために必要な情報を引き出せるに違いありません。ですが、このスタイルが正式に採用されるかどうかは、もう少し様子を見る必要があるでしょう。

 しかし、当座はGoogle Collections Libraryの別の機能を利用すれば、この冗長性を回避できます。

Map<String, List<String>> mapOfLists =
   Maps.newHashMap();
 Maps.newHashMap()は、Google Collections Library内のMapsというユーティリティクラスで定義されている静的メソッドです。これはJavaにおけるジェネリックスの実装上の抜け穴を突いたもので、補助クリエータnewHashMapに型シグニチャを渡すことは実際には必要ないのです。Javaのすべての標準コレクションとGoogleの新しいコレクションのすべてについて、対応する同様の補助クリエータが用意されています。この単純なアイデアだけでコードの可読性がこれほど向上するとは驚きです。

 たとえば、

List<String> listOfStrings =
   new ArrayList<String>();
 は、次のように書き換えることができます。

List<String>
listOfStrings = Lists.newArrayList();
 コレクションと直接は関係ありませんが、Joinクラスのjoinメソッドも便利です。joinメソッドが用意されているPythonのようなスクリプト言語を使ったことがある人なら、すぐわかるはずです。array(配列)、collection(コレクション)、iterable、またはvarargs(可変長引数)によるリストから取り出したアイテムを結合したストリングを作ることができます。最初のパラメータは、結合対象の要素の間に置かれる区切り記号です。たとえば、サブディレクトリのコレクションからファイルのパスを作るには、次のようにします。

String[] subdirs = new String[] {"usr", "local", "lib"};
String directory = Join.join(PATH_SEPARATOR, subdirs);
 取り立てて説明するほど新しい機能ではありませんが、必要なのは多分このメソッドくらいなもので、実際、これを使うことでコードを自作する手間が省けます。

新しいコレクション

 コレクションライブラリという名前から予想されるとおり、新しいコレクションがいくつか用意されています。

Multimap

 再度、前述の例を考えます。これもリストへのマップですが、今度は少し複雑に複数の型を使います。

Map<Integer, List<String>> mapOfLists =
   Maps.newHashMap();
 多分、これと同じようなものを書いたことがある人は少なくないはずです。実際、何かをリストにマップするという操作は、アイテムを集めて識別可能なバケットとして1つにまとめるという操作の共通パターンと見ることができます。このパターンを書いたことがある人なら、マップに新しいアイテムを追加するとき、どのようなコードを書く必要があるかご存じでしょう。

List<String> list = mapOfLists.get(key);
if (list == null) {
   list = new ArrayList<String>();
   mapOfLists.put(key, list);
}
list.add(value);
 つまり、マップに値を追加するときは、その新しい値を受け取るリストを確実に用意しておく必要があるということです。抜かりのない人なら、アイテムを削除するときリストが空かどうかもチェックするはずで、リストが空ならリソースを節約する意味でもリスト自体を削除することでしょう。この種の定型パターンが多用される例を目にすることは、めったにありません。

 Multimapは、これをサポートする実装(厳密には実装の集合)です。実際、ArrayLists、LinkedLists、Treesといった構造をベースとするMultimapが用意されています。

Multimap<Integer, String> numbers =
   Multimaps.newArrayListmultiMap();
numbers.put(1, "One");
numbers.put(1, "Uno");
numbers.put(2, "Two");
numbers.put(2, "Dos");
numbers.remove(1,"One");
numbers.removeAll(2);
 Multimapをさらに突っ込んで調べると、これがとても強力で、もっと奥の深いものであることがわかります。たとえば、リストのマップがあるとき、それらのリストに含まれるすべての値から最大の値を求めようとすると、イテレーションやら比較やらのコードを大量に書く必要があります。Multimapを使うと、これをCollections.max(numbers.values())といった単純な形で書くことができます。このようにMultimapで時間と手間を省ける状況は、いくらでもあります。

Multiset

 Multisetは、おそらくMultimapほど広くは使われないでしょうが、ヒストグラムやカウンタに利用できます。

Multiset<String> histogram =
   Multisets.newHashMultiSet();

histogram.add("Hello");
histogram.add("World", 3);
histogram.add("Hello");
histogram.add("!");

int count;
count = histogram.count("Hello");    // 2
count = histogram.count("World");    // 3
count = histogram.count("!");        // 1
count = histogram.count("Fred");     // 0
 Multisetの実装では、ある効率的なカウンタの実装が内部的に用いられているため、たいていの操作において、MapやListを使うよりもずっと高い性能が得られます。Enums、Hashes、LinkedHashes、Treesといった構造をベースとする実装が用意されています。さらに、ほとんどロックを必要としない完全に並列処理の更新を実現するConcurrentMultisetまであります。

BiMap

 BiMapは、双方向のマップ機能を実現します。双方向マップでは、キーと値が共に一意で、値からキーを引くことも可能です。次に例を示します。

BiMap<Integer, String> numbers = Maps.newHashBiMap();
numbers.put(1, "one");
numbers.put(2, "two");
numbers.put(3, "three");
numbers.put(4, "four");
numbers.put(5, "five");

String one = numbers.get(1);                     // one
int three = numbers.inverse().get("three");      // three
Integer nine = numbers.inverse().get("nine");    // null
// NullPointerException
int oops = numbers.inverse().get("nine");
 別にGoogle Collections Libraryに限った話でありませんが、このような表面的に見えない基本型の変換には警戒が必要です。この例のget("three")コールは問題なく動きます。しかし、get("nine")行でnullポインタの例外が発生します。存在しないキーや値を探すようBiMapに要求することは(通常のマップと同様に)正当な操作であり、当然nullが返されることを想定すべきです。そのIntegerをコンパイラがint型に変換しようとしたときNullPointerExceptionが発生します。

PrimitiveArrays

 Javaは、Objectの下に展開されるオブジェクト指向の階層構造だけでなく、基本型もサポートしています。これはJavaでよく議論される問題点の1つでもあります。すなわち、効率を考えると基本型を積極的に使うべきで、コレクションはオブジェクトの中でのみ扱うべきだという主張です。

 基本型の自動変換機能が追加されたのはJava 5のときなので、コレクションが本当に基本型を受け取って返しているように見せることも不可能ではありません(無論、前述のように基本型の変換に際してNullPointerExceptionが発生する可能性があることを承知した上での話ですが)。しかし、実際には舞台裏で個々の基本型はオブジェクト型に変換されてからコレクションに追加されます。これは、状況によっては内部表現が最適でなくなることを意味します。

 たとえば、100万個の基本型データを含む配列があって、それを(配列でなくコレクションを要求するメソッドか何かのパラメータとして使うために)コレクションとして扱いたいことがあります。あるいは、そのデータがたまにしかアクセスされないことがわかっているのに(たとえば、10個かそこらの要素がコレクションからランダムに取り出される)、その10個がどう選ばれるかわからないこともあります。

 このような状況で基本型の配列からコレクションを作成しても、やたら遅くて、メモリを消費するばかりです。

int[] intArray = new int[1000000];

// Assume it has some data put in it here...

List<Integer> intList = Lists.newArrayList(1000000);
for (int i : intArray) {
   intList.add(i);
}

System.out.println(intList.get(5));
System.out.println(intList.get(707070));
 これでもまあ動きますが、正直冗談のような話です。

 そこで、次のような方法が考えられます。

int[] intArray = new int[1000000];
List<Integer> intList =
   PrimitiveArrays.asList(intArray);
System.out.println(intList.get(5));
System.out.println(intList.get(707070));
 この例で、intListに保持される実装はオリジナルの配列をそのまま保持し、その値はそこから取り出されるときだけオブジェクト型に変換されます。このコレクション内に値を設定することもできますが、渡す値がnullであってはならない(オブジェクトの配列ではそのようなことが起こり得る)という制約が課せられます。

 PrimitiveArraysは、基本型の大きな配列がたまにしかアクセスされないような状況で使うものであり、小さな配列が何度もアクセスされるような状況では、アクセスのたびに基本型の自動変換のオーバーヘッドが生じるので、これを使うべきでありません。

ReferenceMap

 ReferenceMapはかなり特殊なクラスでありながら利用価値がとても高く、キャッシュを実装しようと考えている人にうってつけの機能です。

 キャッシュ(弱参照マップとも呼ばれます)は、アプリケーションの負荷を簡単に切り詰めることができるようにする仕組みです。データベースのような低速の、あるいは高コストの情報源から同じアイテムを何度も取り出さず、必要なアイテムをいったん見つけたら、それをWeakHashMapに格納し、それ以降の検索では、WeakHashMap内に一致するものが既にあるかどうか調べるというものです。WeakHashMap内のキーに対する参照はいずれも弱参照であるため、ガーベジコレクションのとき、そのキーに対する強参照がなければ、その値がヒープから削除されて領域も回収されるため、マップ内のアイテムは直ちに消失します。

 ReferenceMapは、WeakHashMapよりもやや強力で、キーの参照を弱参照に限定せず、弱参照、ソフト参照、強参照の任意の組み合わせをマップのキーと値に許すことで、考え得るあらゆるキャッシュの実装要求に対応できるようにするというものです。

 さらにConcurrentMapもベースとなっているため、並列処理システムでの用途にも最適です。実際、ReferenceMapのStrong:Strongインスタンスは意味論的にConcurrentMapと同一であり、Weak:Strongインスタンスは実質的にWeakHashMapと差し替えることができます。これ以外の組み合わせも自由に使うことができ、キーか値を再利用する場合は、アイテムがReferenceMapから削除されます。

 ソフト参照もあります。ソフト参照の仕様は、そもそもかなりソフトなものですが、弱参照よりやや強めを狙っています。弱参照の場合、ガーベジコレクションが起こると、強参照がない限りオブジェクトは削除されます。一方、ソフト参照の場合は、リソースが必要とされない限り、オブジェクトは保持されます。つまり、ヒープ領域が不足し始めるまでガーベジコレクションは起こらず、オブジェクトはできるだけ長くキャッシュにとどまろうとします。しかし、これは当のVMの実装に依存し、VMがソフト参照もかなり熱心に回収しているように見えることが結構あります。

 以下は初期化の例です。

new ReferenceMap<String,
   Integer>(ReferenceType.WEAK, ReferenceType.SOFT);

不変コレクション

 Java Collections Frameworkが開発された当時、変更不能なコレクションを返す仕組みが導入されましたが、これはコレクションの内容が誤って変更されないようにコレクションにラッパーをかぶせるというものでした。

 しかし、この変更不能コレクションには欠点があります。実は、変更できてしまうのです。変更不能コレクションを受け取った側が内容を変更できなくても、変更不能コレクションのベースとなるオリジナルのコレクションはまだ変更でき、両者がベースのコレクションを共有しているので、そのことが変更不能コレクションに影響します。

 つまり、プログラマが変更不能コレクションを変更できないと決めてかかっても、それは正しくありません。

 Google Collections Libraryでは、これに代わるものとして不変(immutable)コレクションが導入されています。オリジナルのコレクションから複製したコレクションを変更できないようにするので、これを受け取った側はコレクションが決して変更されないことを信頼することができます。

 次に例を示します。

final List<String> immutableList =
   Lists.immutableList("Hello", "World");
// UnsupportedOperationException!
immutableList.add("!");
編集部注
 最新版のライブラリでは、ListsからimmutableListメソッドが削除されています(2008/4/15現在)。以下同様。
 JavaのunmodifiableListとGoogle Collections LibraryのimmutableListとでは、次のような違いが生じます。

final List<String> baseList =
   Lists.newArrayList("Hello", "World");
final List<String> unmodifiableList =
   Collections.unmodifiableList(baseList);
final List<String> immutableList =
   Lists.immutableList(baseList);

baseList.add("!");    // modify the original list

// prints [Hello, World, !] - changed
System.out.println(unmodifiableList);
// prints [Hello, World] - unchanged
System.out.println(immutableList);
 final参照と不変コレクションを組み合わせるやり方がよく用いられますが、それでも安心してはいけません。コレクション自体を変更できなくても(つまり、要素の追加、削除、置換ができなくても)、コレクション内の可変オブジェクトはまだ変更できるからです。そのため、不変コレクションに可変値を格納しないようにすることをお勧めします。無用な混乱を招くだけでなく、不変コレクションを使うことの意義が失われるからです。無論、例外は必ずありますが、不変と可変を混在させるときは、ドキュメントにその理由と経緯を明記しておくべきです。

関数型プログラミング的な機能

 ここまで読まれた方は、Google Collections Libraryが、かなりまともでストレスを感じさせないコレクションだと知って、これからダウンロードしてみようと考えておられるかもしれません。しかし、ちょっと待ってください。まだ、嬉しいものがあります。

 Javaは関数型言語でありませんが、関数の考え方を少なくとも限定されたスコープで実に簡単に用いることができ、それによって可読性と効率を大きく高めることができます。

 たとえば、自動車のリストがあり、それぞれの自動車は名前と価格を持つものと仮定します。このリストから価格の高い自動車をすべて抜き出して自動車のコレクションを作り、それをプログラムの中で調査することを考えます。

 従来のやり方なら、自動車のコレクションをループで走査して価格をチェックし、価格が一定の限度を超えたら、その自動車に関する何らかの処理を実行するか、その自動車を別の新しい高価格自動車コレクションにコピーすることになるでしょう。

 Google Collections Libraryには、これを別のやり方で実現するプレディケート(Predicate)という仕組みが用意されています。

プレディケート(Predicate)

 プレディケートとは、クラスのインスタンスに基づいて選択基準を指定し、そのクラスに対応するインスタンスをコレクションから選択的に抽出できるようにする仕組みです。次の例を見れば、この機能がよくわかるでしょう。

final Predicate<Car> expensiveCar = new Predicate<Car>() {
   public boolean apply(Car car) {
      return car.price > 50000;
   }
};

List<Car> cars = Lists.newArrayList();
cars.add(new Car("Ford Taurus", 20000));
cars.add(new Car("Tesla", 90000));
cars.add(new Car("Toyota Camry", 25000));
cars.add(new Car("McClaren F1", 600000));

final List<Car> premiumCars =
   Lists.immutableList(Iterables.filter(cars, expensiveCar));
 この例では、expensiveCarというプレディケートを作成しています。これは自動車の価格が50000を超えるとtrueを返します。プレディケートのapplyメソッドには選択基準を記述する必要があります。これを自動車のリストにIterables.filter()またはIterators.filter()で適用すると、premiumCarsの不変リスト(TeslaとMcLaren F1のみを含む)が作られます。

 Javaの内部クラスの構文のインパクトが薄れる面はありますが、関数的な処理としては、かなりエレガントです(プレディケートをインラインで定義しようとすると、無名内部クラスの構文を使う必要があります)。Java 7ではクロージャがいくつか提案されており、それを使えば、もっと簡潔に書けるでしょう。いずれにせよ、この簡単な例だけでははっきりしませんが、コードがもっと複雑になれば、ループを使った方法よりもずっと簡潔で可読性が高くなることがわかるはずです。

関数(Function)

 プレディケートがリストを抽出するのと同様に、関数はコレクション内の要素を変換して別の新しいコレクション(型が同じとは限らない)を作る仕組みを提供します。これは多くの関数型言語のマップ機能のようなものです。

 次に例を示します。

final Function<Integer, String> nameForNumber =
   new Function<Integer, String>() {
   public String apply(Integer from) {
      return numbers.get(from);
   }
};

List<Integer> sequence =
   Lists.newArrayList(new Integer[] {1,2,3,5,3,1,4});

for (String name : Iterables.transform(sequence,
   nameForNumber)) {
   System.out.println(name);
}
 new Functionのジェネリック(総称的)な型シグニチャは、この関数がIntegerを受け取り、それを何らかの方法でStringに変換することを示唆しています。applyメソッドは、これらのジェネリックな型に従い、Integer型のfrom値を受け取ってString型の値を返します。

 この例では、Integerを対応するString名にマップするために、以前作成した双方向マップ(BiMap)のnumbersを使用しています。その後、Iterables.transform関数でIntegerのArrayListを変換して対応する名前を求めます。簡単で、しかもエレガントなやり方です。

 この例から関数の効用はよくわかりますが、実際には同じことがもっと簡単にできます。Functionsクラスに静的な補助メソッドがいくつか定義されており、よく使う関数はそれらのメソッドで提供されます。たとえば、そのうちの1つであるforMap()は、マップを受け取り、そのマップをコレクションに適用する関数を作ります。したがって、これを上の例に当てはめると、Function内部クラスの定義を次のように書き換えることができます。

Function<Integer, String> nameForNumber =
   Functions.forMap(numbers);
 同様のクラスにPredicatesとConstraintsがあり、よく使うプレディケートや制約があらかじめ定義されています。

制約(Constraint)

 このライブラリにおいて、関数的なプログラミングを実現するもう1つの仕組みは制約です。制約とはコレクションにどのような値を追加してよいかを追加的にコントロールするものです。このライブラリの制約は、まだまだ大きく変更されているので、ここで詳しい例を示すことはしません。これを使用した場合、Google Collections Libraryをアップグレードする際、それまでに作成したコードのアップデートが必要になる公算が大きくなります。

ステータス

 0.5というバージョン番号に反して、このライブラリは信頼性も含めて相当なレベルまで仕上がっていますが、まだ完成していないことを肝に銘じてください。具体的に言えば、このライブラリはJava 5とJava 6のどちらでも動きますが、Java 6のすべての機能に対応しているわけではありません(たとえば、Navigableインターフェイスには対応していません)。これから登場するバージョンはJava 6の機能に対応したものとなりますが、Java 5に対応するバージョンは今後もJava 7が登場するまで引き続きメンテナンスされます。

結論

 Google Collections Libraryを使うと、生産性が向上するのは言うまでもなく、コードの可読性が大きく改善される可能性があります。本稿が、Google Collections Libraryをプロジェクトで利用しようと考えている人にとって有用な判断材料となれば幸いです。このライブラリはオープンソースなので、リスクは比較的小さいですが、Java 7の規格が現時点でどちらに向かうかまだはっきりしない部分があるので、将来、規格に適合させるためにリファクタリングが必要になる可能性があります。これ以外の方法としては、別のコレクションライブラリを使うか、自分の手でライブラリを書くか、標準のライブラリだけで済ますか、そのいずれかの道が考えられますが、長期的に見ればどれも同様の問題を抱えることになります。

 私自身について言うなら、確かにGoogle Collections Libraryによって多くの時間を節約でき、コードも改善されたので、今後も可能なら仕事のみならず個人のプロジェクトでも使っていくつもりです。

 本稿をGoogle Collections Libraryチームのメンバにレビューしてもらっているとき、新バージョンが準備中であることを知りました。次期バージョンのリリース日がまだ確定していないので、とりあえず本稿を先に出すことにしましたが、次のバージョンが手に入ったら、この場で変更点を再び取り上げるつもりです。

謝辞

 本稿を執筆するに当たって、心に決めたことがあります。1つは偉大なライブラリの存在を広く伝えることです。もう1つは入門的な記事を書くことに努め、それを超えて自分の功績を主張しないようにすることです。そのようなわけで、まずは、この数年にわたって本ライブラリの開発と改良に取り組んできたGoogle社の偉大な技術者たちに謝意を表したいと思います。最初、ソースコードに書かれている名前を抜き出そうとしたのですが、よく考えてみると、それではライブラリの改良に尽力した原作者以外の人々に感謝することになりません。そこで、Google社の多くの技術者が本ライブラリの開発と改良に携わったことを述べるにとどめました。

 Kevin BourrillionとJared Levyには特に感謝します。彼らは本ライブラリをオープンソース化するための準備に尽力してくれました。また、実際にオープンソースとなるまでの経緯を見守り、今後も引き続き改良することを約束してくれました。

著者紹介

Dick Wall(Dick Wall)
Google社(カリフォルニア州マウンテンビュー)の技術者で、Javaテクノロジーの提唱者。Google Developer Programに所属。Java Posseポッドキャスト(http://javaposse.com)の共同司会者も務める。Java PosseではJava関連のニュースやインタビューを定期的に放送している。
このエントリーを含むはてなブックマーク この記事をクリップ!
BuzzurlにブックマークBuzzurlにブックマーク Yahoo!ブックマークに登録
この記事をokyuuへインポート
最新トップニュース
データメーション
【データメーション】
中国が「Green Dam」フィルタ規制を撤回(7月1日)
Graphic Design Forum
【Graphic Design Forum】
Chris Dickman(6月25日)
プライバシー ジャパン・インターネットコム版
【プライバシー ジャパン・インターネットコム版】
グーグル・ストリートビューの問題について総務省の見解(6月23日)
エンジニアの独り言
【エンジニアの独り言】
システムを「使う」時代のエンジニアに求められるもの(6月2日)
最新ハイテク講座
最新ハイテク講座
電気は家庭でつくる時代へ!燃料電池「エネファーム」(7月3日)
アクセス解析で見るWebマーケティング
アクセス解析で見るWebマーケティング
決定力を探るアクセス解析(7月3日)
百式のネットビジネス研究
百式のネットビジネス研究
ファーストフードを高級っぽく盛り付けて紹介している「Fancy Fast Food」(7月3日)
週刊-サイト別アクセス状況データ
週刊-サイト別アクセス状況データ
ビデオリサーチインタラクティブ調査(月間インターネットオーディエンスデータ)(7月2日)
成約率、反応率を上げる Web 文章術
成約率、反応率を上げる Web 文章術
言葉がダイレクトにキャッシュを生む(7月2日)
不況時代の Web ビジネス最適化講座
不況時代の Web ビジネス最適化講座
アクセス解析エキスパートここだけの話、Web コンシェルジュの“勉強法”こっそり教えます(7月2日)
「Webからの脅威」―その傾向と最新対策
「Webからの脅威」―その傾向と最新対策
不正プログラムの分類(7月1日)
DevX
DevX
JavaScriptとDOMによる動的なWebページの作成(6月30日)
エンジニア転職ノウハウ開発室
エンジニア転職ノウハウ開発室
今のままで大丈夫?3匹の子ブタ的キャリア危険度診断(6月30日)
アイレップの SEM フロンティア
アイレップの SEM フロンティア
Web サイトは「無駄な穴のたくさん開いたじょうご」〜サイト成果向上の基本的な考え方(6月30日)
Copyright 2009 Japan Internet.com K.K. All Rights Reserved.http://www.internet.com/