Collection (データの集合)

データの集まりを表現するのがCollectionです。

参考資料

Collectionに関してはJava Tutorialが詳しい

JavaのCollection関連クラスのInterface階層

Collection interfacedeは以下のようなメソッドを、List, Setなどの共通項として持っている。

つまり、List, Setインターフェースを実装しているLinkedList, ArrayList, TreeSet, HashSetなどでこれらのメソッドを共通して使えることになる。

public interface Collection<E> extends Iterable<E> {
    // Basic operations
    int size();   // 含まれる要素の数を返す
    boolean isEmpty(); // 空のとき、true
    boolean contains(Object element); // 要素elementを含んでいるならtrue 
    boolean add(E element);         //optional 新しい要素elementを追加する
    boolean remove(Object element); //optional 要素element を削除する
    Iterator<E> iterator(); // 全要素を巡回するためのIteratorを返す

    // Bulk operations
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c); //optional
    boolean removeAll(Collection<?> c);        //optional
    boolean retainAll(Collection<?> c);        //optional
    void clear();                              //optional 空っぽにする

    // Array operations
    Object[] toArray();
    <T> T[] toArray(T[] a);
}

使用例

1から10までの数を含むリストを作りたい

ArrayList<Integer> intList = new ArrayList<Integer>();

for(int i=1; i<=10; i++)
  intList.add(i);

for(int val : intList)
{
   System.out.println(val);
}

結果

1
2
3
4
5
6
7
8
9
10

順不同なデータを、ソート(並び替え)したい

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(10);  // [10]
intList.add(3);   // [10, 3]
intList.add(5);   // [10, 3, 5]
intList.add(5);   // [10, 3, 5, 5]
intList.add(15);  // [10, 3, 5, 5, 15]

System.out.println(intList); // [10, 3, 5, 5, 15]
    
Collections.sort(intList);
    
System.out.println(intList); // [3, 5, 5, 10, 15]

常にソートされた状態にしておきたい (ただし重複はなし)

TreeSet<Integer> sortedSet = new TreeSet<Integer>();
sortedSet.add(10);  // [10]
sortedSet.add(3);   // [3, 10]
sortedSet.add(1);   // [1, 3, 10]
sortedSet.add(8);   // [1, 3, 8, 10]

重複ありの場合

重複ありのリストを管理したい場合には、Google Collection LibraryにあるMultiSet, MultiMapなどを使うとよいかもしれないが、Comparatorを工夫して、要素の比較演算を自ら定義すると、SortedSetなどでも重複データは扱える。

自分で作ったオブジェクトを格納したい

レポート課題のGenomeSequenceクラスを例に。GenomeSequenceクラスにtoString()メソッドを定義しておくと、コレクションの中身の確認がしやすくなります。

public String toString() {
   return String.format("%s: %s", name, sequence);
}

LinkedList<GenomeSequence> sequenceList = new LinkedList<GenomeSequence>();
sequenceList.add(new GenomeSequence("seq0001", "AATTGG"));
sequenceList.add(new GenomeSequence("seq0003", "TAG"));
sequenceList.add(new GenomeSequence("seq0002", "CCGGGCC"));
sequenceList.add(new GenomeSequence("seq0010", "CGTTT"));
sequenceList.add(new GenomeSequence("seq000A", "ACGT"));

LinkedListとArrayList

LinkedListは数珠つなぎに要素を結合していくデータ構造で挿入に強い。ArrayListの中身は固定長の配列。要素の挿入時に、挿入位置より後ろの要素をすべて移動しなくてはならないので、速度が遅くなるときがある。

自分で作ったオブジェクトを並び替えたい

Comparatorを使う方法

LinkedList<GenomeSequence> sequenceList = new LinkedList<GenomeSequence>();
sequenceList.add(new GenomeSequence("seq0001", "AATTGG"));
sequenceList.add(new GenomeSequence("seq0003", "TAG"));
sequenceList.add(new GenomeSequence("seq0002", "CCGGGCC"));
sequenceList.add(new GenomeSequence("seq0010", "CGTTT"));
sequenceList.add(new GenomeSequence("seq000A", "ACGT"));

System.out.println(sequenceList);
// [seq0001: AATTGG, seq0003: TAG, seq0002: CCGGGCC, seq0010: CGTTT, seq000A: ACGT]


Collections.sort(sequenceList, new Comparator<GenomeSequence>() {
  // クラス定義をメソッド呼び出し中で行うというテクニック
   public int compare(GenomeSequence s1, GenomeSequence s2) {
	return s1.getName().compareTo(s2.getName());
    } 
});

System.out.println(sequenceList);
// [seq0001: AATTGG, seq0002: CCGGGCC, seq0003: TAG, seq000A: ACGT, seq0010: CGTTT]

Comparableを実装し、Setを利用する方法

sequenceの名前の辞書順で並び替えを行いたい場合

class GenomeSequence implements Comparable<GenomeSequence> {

(中略)
  public int compareTo(GenomeSequence other) {
	return name.compareTo(other.getName());
  }
}

TreeSet<GenomeSequence> sequenceSet = new TreeSet<GenomeSequence>();
sequenceSet.add(new GenomeSequence("seq0001", "AATTGG"));
sequenceSet.add(new GenomeSequence("seq0003", "TAG"));
sequenceSet.add(new GenomeSequence("seq0002", "CCGGGCC"));
sequenceSet.add(new GenomeSequence("seq0010", "CGTTT"));
sequenceSet.add(new GenomeSequence("seq000A", "ACGT"));

System.out.println(sequenceSet);
// [seq0001: AATTGG, seq0002: CCGGGCC, seq0003: TAG, seq000A: ACGT, seq0010: CGTTT]

Iteratorを使ってCollectionを巡回したい

TreeSet<GenomeSequence> sequenceSet = new TreeSet<GenomeSequence>();
sequenceSet.add(new GenomeSequence("seq0001", "AATTGG"));
sequenceSet.add(new GenomeSequence("seq0003", "TAG"));
sequenceSet.add(new GenomeSequence("seq0002", "CCGGGCC"));
sequenceSet.add(new GenomeSequence("seq0010", "CGTTT"));
sequenceSet.add(new GenomeSequence("seq000A", "ACGT"));

for(Iterator<GenomeSequence> it = sequenceSet.iterator(); it.hasNext(); )
{
   GenomeSequence seq = it.next();
   System.out.println(seq.toString());
}
/*
以下のように表示される
seq0001: AATTGG
seq0002: CCGGGCC
seq0003: TAG
seq000A: ACGT
seq0010: CGTTT
*/

IteratorはSet, Listなどに共通して使える。

データに早くアクセスするための索引を作りたい Map

LinkedList<GenomeSequence> sequenceList = new LinkedList<GenomeSequence>();
sequenceList.add(new GenomeSequence ("seq0001", "AATTGG"));
sequenceList.add(new GenomeSequence("seq0003", "TAG"));
sequenceList.add(new GenomeSequence("seq0002", "CCGGGCC"));
sequenceList.add(new GenomeSequence("seq0010", "CGTTT"));
sequenceList.add(new GenomeSequence("seq000A", "ACGT"));

// HashMapでは、(key, value)のペアを格納し、keyをO(1)で検索できる
HashMap<String, GenomeSequence> sequenceIndex = new HashMap<String, GenomeSequence>();
for(GenomeSequence seq : sequenceList){
   sequenceIndex.put(seq.getName(), seq);
}

GenomeSequence searchResult = sequenceIndex.get("seq0001");
// searchResult = ("seq0001", "AATTGG")
GenomeSequence searchResult2 = sequenceIndex.get("seq00XX");
// searchResult == null

if(sequenceIndex.containsKey("seq000A"))
{
   // seq000Aが見つかった
   GenomeSequence seq00A = sequenceIndex.get("seq0001");
}

// HashMapでは、keyはsortされていない
for(String key : sequenceIndex.keySet())
{
   System.out.println(sequenceIndex.get(key));
}
/* 
以下のように(順不同)で表示される
seq0010: CGTTT
seq0001: AATTGG
seq0003: TAG
seq000A: ACGT
seq0002: CCGGGCC

HashMapをTreeMapに変更すると、以下のようになる。
seq0001: AATTGG
seq0002: CCGGGCC
seq0003: TAG
seq000A: ACGT
seq0010: CGTTT
*/

HashMap とTreeMap

HashMapとTreeMapはどちらも(key, value)のペアを格納し、keyの値でvalueを検索するの性能に優れています。keyの並び順を気にしなくてもいい場合は、HashMapの方がO(1) (定数オーダー)で、性能が安定していますが、keyをソートした順でデータを管理したい場合は、TreeMapを使います。TreeMapでは、内部で木構造を使ってデータを管理しているので、keyの検索性能はO(log n)です(nは要素の数、log nは木の高さ)