SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

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

Google発のコレクションライブラリの紹介

新しいコレクション

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

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);

次のページ
不変コレクション

この記事は参考になりましたか?

japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Dick Wall(Dick Wall)

Google社(カリフォルニア州マウンテンビュー)の技術者で、Javaテクノロジーの提唱者。Google Developer Programに所属。Java Posseポッドキャスト(http://javaposse.com)の共同司会者も務める。Java PosseではJava関連のニュースやインタビューを定期的に放送している。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/2397 2008/04/21 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー