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
- ArrayList
- Set
- EnumSet
- HashSet
- TreeSet
- Queue
- PriorityQueue
- Bag (*) 実装はHashBag, TreeBag。特筆事項なし。
- Buffer (*) 実装はArrayStack。特筆事項なし。
- BoundedCollection (*) FixedSizeList(下記)
- List
- Map
- HashMap
- LinkedHashMap
- TreeMap
- EnumMap
- IdentityHashMap
- WeakMap
- BoundedMap (*) FixedSizeMapその他(下記)
- IterableMap (*)
- BidiMap (*) 実装はDualHashBidiMap, DualTreeBidiMap。特筆事項なし。
- OrderedMap (*) LinkedMap, ListOrderedMap(下記)
- BeanMap (*) (下記)
- HashMap
- 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。キャッシュ等に便利)など、妙なコレクションは他にもまだまだあります。興味のある方は掘って見ると面白いです。