Apache commonsが便利な件(commons-collections編-1)

http://d.hatena.ne.jp/daisuke-m/20080702/1214982943

再びユーティリティ系。ということで今回はcommons-collections。

commons-collections は、Javaコレクションフレームワーク(っていうと大仰だけど、要はListとかSetとかMap。以下JCF)まわりのユーティリティ。

ところで、JCFまわりは Java1.4から5.0へのバージョンアップにあたり、ジェネリクスが導入されて、非常に使いやすくなりました。今やジェネリクスのないコレクションなんて、怖くて触りたくない病に罹っている今日この頃です。

しかし、このcommons-collectionsは下位互換を理由にジェネリクス化されていません。ジェネリクス世代としては、敬遠せざるを得ません><。

が、どうやら本家Apacheからフォークしてcommons-collectionsをgenerics化したライブラリがあります。

  1. http://sourceforge.net/projects/collections
  2. http://larvalabs.com/collections/

ここでは、1番のライブラリにフォーカスを当ててご紹介します。

CollectionUtils, ListUtils, SetUtils, MapUtils

毎度おなじみ、ユーティリティ。コレクションを「集合」として扱い、そのunion, intersection, disjunction,
subtract の算出なんてのも。あんま使わない?

ListUtils.EMPTY_LIST 等が定数定義されている。コードで語るためのおなじみですね。

他に、ListUtils.isEqualList() は「2つのリストの要素と順番が一致するかどうか」トカトカ。

CollectionUtils.get(x, 3)は、xがMapだったとしても3番目の要素が取れるとか、ちょっと変態なモノも。気になれば、読んでみるほうが早い。


さて、実はcommons-collectionの要はUtilsではない。様々なコレクションの実装が目玉です。また、List, Set,
Map の他に、新しいパラダイムのコレクションを定義し、その実装を提供しています。今回は新パラダイムの紹介。

Bag

追加された要素の数を数えるコレクションです。

Bag bag = ...;
bag.add(a);
bag.add(a);
bag.add(b);
bag.add(c);
assert bag.getCount(a) == 2;
assert bag.getCount(b) == 1;
assert bag.getCount(c) == 1;

このインターフェイスはCollectionを継承していますが、リスコフの置換原則に違反しています。二度目以降にaddされたオブジェクトはコレクション内に保持されず、単純にカウンタを増やすだけ、という動作をします。BagのインスタンスをCollectionとして扱う際は、注意しましょう。

Buffer

この型もCollectionを継承したもので、「要素を特定の順番に基づいて取得・削除する」操作を定義しています。代表的なのは「スタック」と「キュー」ですね。FIFOとFILO。その他の順序に基づかせることも可能です。どんな順番なのかは、実装が定義します。

JCFにはStackも定義されていますが、可能ならばStack型とQueue型ににBufferをimplementsしたいところですw

Buffer buffer = ...; // FILO(Queue)だと仮定する
buffer.add(a);
buffer.add(b);
buffer.add(c);
assert buffer.get() == c;
assert buffer.remove() == c;
assert buffer.remove() == b;
assert buffer.remove() == a;
buffer.get(); // BufferUnderflowException

MultiMap (MultiHashMap)

Mapは、同じキーのエントリを複数持つことができず、既に同じキーのエントリを保持していた場合は、上書きされる仕様となっています。

しかしこのMultiMapは、複数のエントリを持つことができます。従って、このタイプは、Mapのサブタイプではありません。サブタイプ化すると、リスコフの置換原則に違反してしまいます。

Number key = new Integer(5);
MultiMap<Number,String> mm = new MultiHashMap<Number,String>();
mm.put(key, "a");
mm.put(key, "b");
mm.put(key, "c");
Collection<String> coll = mm.get(key);  // == [a,b,c]

BidiMap

Bidirectional map の略です。双方向マップ、ですね。

何が双方向なのか。一般的なMapは、keyからvalueを引くことができますが、valueからkeyを引くことはできません。頑張れば(エントリを全検索…)取得することもできますが、パフォーマンスは最悪です。

BidiMapでは双方向のルックアップが、同じパフォーマンスで可能です。ただし、Mapの「keyが衝突したときに上書きされてしまう」という特性がvalue側にも適用されてしまいます。

BidiMap bm = ...;
bm.put(a, b);
assert bm.get(a) == b;
assert bm.getKey(b) == a;

bm.put(a, c);
assert bm.get(a) == c;
assert bm.getKey(c) == a;

bm.put(b, c);
assert bm.get(a) == null;
assert bm.get(b) == c;
assert bm.getKey(c) == b;

BoundedCollection, BoundedMap

容量制限付きコレクション。

BoundedCollection bc = ...;     // 制限2
assert bc.isFull() == false;
assert bc.maxSize() == 2;
bc.add(a);
bc.add(b);
assert bc.isFull() == true;
bc.add(c); // BufferOverflowException

さて、長くなってきたので、次回に続く。次回は各実装についてー。