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

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

前回に引き続きcommons-collections。

Java Collection Framework(JCF)では、大きく分けてCollectionとMapの2系統の概念が定義されています。そして、前回はこれらに加えて Bag, Buffer などの追加された概念を説明しました。それぞれの代表的なタイプを整理したものが以下*1

  • Collection
    • List
      • ArrayList
        • ArrayStack (*) 実装はArrayStack。特筆事項なし。
      • LinkedList
      • Stack
    • Set
      • EnumSet
      • HashSet
      • TreeSet
    • Queue
      • PriorityQueue
    • Bag (*) 実装はHashBag, TreeBag。特筆事項なし。
    • Buffer (*) 実装はArrayStack。特筆事項なし。
    • BoundedCollection (*) FixedSizeList(下記)
  • Map
    • HashMap
      • LinkedHashMap
    • TreeMap
    • EnumMap
    • IdentityHashMap
    • WeakMap
    • BoundedMap (*) FixedSizeMapその他(下記)
    • IterableMap (*)
      • BidiMap (*) 実装はDualHashBidiMap, DualTreeBidiMap。特筆事項なし。
      • OrderedMap (*) LinkedMap, ListOrderedMap(下記)
    • BeanMap (*) (下記)
  • MultiMap (*) 実装はMultiHashMap。特筆事項なし。

JCF定義の各タイプの特徴は、ググればあちこちに書いてあるのでそちらに譲るとします。commons-collectionsにて追加されたタイプ(*)については、前回参照。

今回は、これらの実装にはどのようなものがあるか、がテーマです。ココがcommons-collectionのキモです。

LinkedMap, ListOrderedMap

HashMapはhashCodeに基づく順序、TreeMapはComparatorに基づく順序で保持されますが、JCF標準ではput順を維持してくれるMapは存在しません。この仕様を提供するのがこれらの実装です。

get(index), getValue(index) などが提供されています。

FixedSize〜

Boundedの「上限付き」に制限をさらに追加し「要素数が一定」としたものです。
FixedSizeListが代表的。add, remove,
clear等はUnsupportedOperationExceptionがトビます。要素の置換はsetを経由して行います。

Unmodifiable〜

読むことはできても改変することはできないよ、というコレクション。デコレータです。

public class Foo {
 private List<String> bar = new ArrayList<String>();
 public List<String> getBar() {
   return bar;
 }
}

例えばこんなコード。Fooの外にbarを見せたいけど、勝手に要素を追加削除とか(foo.getBar().add(...)とか出来ちゃうからね…)されたくない時。よくある対策は、コピーコンストラクタなどで複製を返してやること。しかし、クライアント側では追加が通るので、「取得元のコレクション実体が変更されたのかどうか」が分かりづらいことになります。そこでコレ。

Unmodifiableはデコレータなので、実装を装飾して返してあげる。

public class Foo {
 private List<String> bar = new ArrayList<String>();
 public List<String> getBar() {
   return UnmodifiableList.decorate(bar);
 }
}

これで、getBar() したListに対してaddとかすると、UnsupportedOperationExceptionをブッ飛ばしてくれる。すばらしい。

Synchronized〜

JCFのコレクションは(Vector, Hashtableを除き)スレッドセーフではありません。これらをスレッドセーフに動作するように修飾(デコレーション)するのがコレ。

List<E> list = SynchronizedList.decorate(new ArrayList<E>());

Predicated〜

前提条件付きコレクション。特定の条件を満たすインスタンスしか受け付けないコレクション。条件を満たさないインスタンスを追加するとIllegalArgumentExceptionが飛ぶ。
条件はPredicateインターフェイスを実装して設定する。

例えばこれは10文字未満の文字列しか受け付けないリスト。

PredicatedList pl = PredicatedList.decorate(new ArrayList<String>(), new Predicate<String>() {
    public boolean evaluate(String str) {
      return str != null && str.length() < 10;
    }
  });
pl.add("123");
pl.add("12345");
pl.add("1234567890"); // IllegalArgumentException

このようにPredicateを自分で実装してもかまわないが、一般的な条件はfunctorsパッケージに用意されているので調べてみるといい。

Transformed〜

自動変換機能付きコレクション。結構邪悪である。addされる際、指定した変換を施してから保持を行う。変換ロジックはTransformerインターフェイスを実装して設定する。

例えばこれは、すべてのインスタンスを文字列化して保持するリスト。

List pl = TransformedList.decorate(new ArrayList<String>(), new Transformer<Object, String>() {
    public String transform(Object input) {
      return input.toString();
    }
  });
pl.add(new Object());
pl.add(new int[] {1, 2});
pl.add("abc");
pl.add(12.3);
System.out.println(pl); // [java.lang.Object@118f375, [I@117a8bd, abc, 12.3]

IdentityMap

equalsじゃなくて==に基づいてキーの一意性を判別するMap。一言で説明済んじゃうな。

CaseInsensitiveMap

keyがString限定で、大文字小文字を区別しないMap。言い換えれば、equalsじゃなくてequalsIgnoreCaseに基づく訳ですね。

MultiKeyMap

複合キーMap。RDBMSのテーブルにある「複合キー」の考え方を取り入れたMap。

MultiKeyMap<String, Object> mkm = new MultiKeyMap<String, Object>();
mkm.put(new MultiKey<String>("1", "1", "1"), a);
mkm.put(new MultiKey<String>("1", "2", "1"), b);
mkm.put(new MultiKey<String>("1", "2", "2"), c);
assert mkm.get("1", "1", "1") == a;
assert mkm.get("1", "2", "1") == b;
assert mkm.get("1", "2", "2") == c;
assert mkm.get("0", "2", "2") == null;

その他、SetUniqueList(重複addを無視するList)、Flat3Map(3要素まではフィールドに値を保持し、高パフォーマンスを出すMap)、ReferenceMap(CGが掛かると値が消えるMap。キャッシュ等に便利)など、妙なコレクションは他にもまだまだあります。興味のある方は掘って見ると面白いです。

*1:コンカレント系, Vector, Hashtableを除く