前回では、コレクションフレームワークに関係のあるクラスのうち、具体的な実装クラスについて紹介しました。今回はさらに踏み込んで、コレクションフレームワークと密接な関係のある、各要素にアクセスすためのクラスやソートするためのクラスなど、非常に便利なクラス群を紹介します。
Collectionの中に含まれる各要素にアクセスする場合、java.util.Iteratorを使用することになります。
java.util.Collectionインターフェースには次のようなメソッドがあります。
このメソッドを使用してIteratorを取得することにより、簡単にCollectionの各要素にアクセスできます。
java.util.Iteratorが持つメソッドのうち、特に重要な2つを挙げます。
この2つのメソッドを使用すると、配列の各要素にアクセスするのと同様に、Collectionの各要素にアクセス可能です。
import java.util.Collection; import java.util.Iterator; import java.util.HashSet; public class Iteration { public static void main(String[] args) { Collection c = new HashSet(); c.add("hoge"); c.add("foo"); c.add("bar"); for (Iterator i = c.iterator(); i.hasNext();) { String element = (String) i.next(); System.out.println(element); } } }
> java Iteration foo bar hoge
MapはCollectionではないため、先ほどと同様の方法ではイテレーションできません。
そこで、entrySet()メソッドを使用して、Collectionに変換してから行います。
import java.util.Iterator; import java.util.Map; import java.util.HashMap; public class MapIteration { public static void main(String[] args) { Map m = new HashMap(); m.put("hoge", "123"); m.put("foo", "456"); m.put("bar", "789"); for (Iterator i = m.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); String key = (String) entry.getKey(); String value = (String) entry.getValue(); System.out.println(key + " => " + value); } } }
> java MapIteration foo => 456 bar => 789 hoge => 123
Listに値を格納していくと、最終的な順序は格納した順番と同じものになります。
この中身をソートしたい場面があるかもしれません。
ソートは速いアルゴリズムを書くのが比較的面倒で、バグを生み出しやすくなります。ここでは、java.util.Collections.sort(List)を使用したソートを紹介します。
import java.util.List; import java.util.Arrays; import java.util.Collections; public class SortList { public static void main(String[] args) { List l = Arrays.asList(new String[]{ "hoge", "foo", "bar", "hogehoge", "foofoo", "barbar", "1", "2", "3", "100", "200" }); System.out.println(l.toString()); Collections.sort(l); System.out.println(l.toString()); } }
> java SortList [hoge, foo, bar, hogehoge, foofoo, barbar, 1, 2, 3, 100, 200] [1, 100, 2, 200, 3, bar, barbar, foo, foofoo, hoge, hogehoge]
java.util.HashSetやjava.util.HashMapを使用すると、値またはキーの順序はランダムになります。
これらをソートする一番簡単な方法は、java.util.TreeSetやjava.util.TreeMapを使用するというものです。
import java.util.Set; import java.util.HashSet; import java.util.TreeSet; public class SortSet { public static void main(String[] args) { Set s = new HashSet(); s.add("hoge"); s.add("foo"); s.add("bar"); s.add("hogehoge"); s.add("foofoo"); s.add("barbar"); System.out.println(s.toString()); s = new TreeSet(s); System.out.println(s.toString()); } }
> java SortSet [foo, barbar, bar, hoge, hogehoge, foofoo] [bar, barbar, foo, foofoo, hoge, hogehoge]
次のプログラムを見てください。
import java.util.List; import java.util.Arrays; import java.util.Collections; public class LexicographicSort { public static void main(String[] args) { List l = Arrays.asList(new String[]{ "100", "200", "10", "20", "30", "1", "2", "3" }); Collections.sort(l); System.out.println(l.toString()); } }
> java LexicographicSort [1, 10, 100, 2, 20, 200, 3, 30]
結果を見ても分かるように、数値も辞書式配列でソートされるために正しくソートされません。
そこで、java.util.Comparatorというインターフェースを使用して、比較の方法を定義します。
import java.util.Comparator; import java.util.List; import java.util.Arrays; import java.util.Collections; public class NumericSort { public static void main(String[] args) { List l = Arrays.asList(new String[]{ "100", "200", "10", "20", "30", "1", "2", "3" }); Collections.sort(l, new NumericComparator()); System.out.println(l.toString()); } } class NumericComparator implements Comparator { public int compare(Object o1, Object o2) { String s1 = (String) o1; String s2 = (String) o2; return Double.valueOf(s1).compareTo(Double.valueOf(s2)); } }
> java NumericSort [1, 2, 3, 10, 20, 30, 100, 200]
ここで作成したNumericComparatorクラスは、TreeSetやTreeMapのコンストラクタに引数に取ることによって、各要素の順序付けを決めるルールにも使用することができます。
Collectionフレームワークに含まれるクラスは、基本的に変更可能でスレッドアンセーフです。
変更不可能なコレクションや、スレッドセーフなコレクションを作成するには、自分でわざわざクラスを作成するまでもなく、簡単な方法で実現できます。
メソッドからコレクションを返す際に、防御的コピーを毎回とったり、配列に変換したりするのは面倒です。その場合に、変更不可能なコレクションとしてメソッドから返すという方法が考えられます。
そこで、java.util.CollectionsクラスのunmodifiableCollection(Collection)というメソッドを使用すると、現在使用しているCollectionを変更不可能にすることができます。
import java.util.Collections; import java.util.Collection; import java.util.ArrayList; public class Unmodifiable { public static void main(String[] args) { // modifiable collection Collection c = new ArrayList(); c.add("hoge"); System.out.println(c); c.add("foo"); System.out.println(c); c.add("bar"); System.out.println(c); // *** make it unmodifiable *** c = Collections.unmodifiableCollection(c); // ERROR ! c.add("moge"); System.out.println(c); } }
> java Unmodifiable [hoge] [hoge, foo] [hoge, foo, bar] Exception in thread "main" java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableCollection.add(Unknown Source) at Unmodifiable.main(Unmodifiable.java:20)
mogeが追加される前に例外が発生していることを確認できます。
このほかにもjava.util.Collectionsには以下のようなメソッドがあります。
次のプログラムを実行した結果を見てみます。
import java.util.List; import java.util.LinkedList; public class Loop extends Thread { private final List list; private static int total = 0; private Loop(List list) { this.list = list; } public void run() { int count = 0; // consume while (list.remove(null)) { count++; } System.out.println("consume " + count + " elements"); synchronized (this.getClass()) { total += count; } } public static void main(String[] args) throws InterruptedException { // *** thread UNSAFE list *** List l = new LinkedList(); // prepare 100,000 elements for (int i = 0; i < 100000; i++) l.add(null); Thread[] loops = new Thread[] { new Loop(l), new Loop(l), new Loop(l), new Loop(l), new Loop(l) }; // start 5 threads for (int i = 0; i < loops.length; i++) loops[i].start(); // wait for (int i = 0; i < loops.length; i++) loops[i].join(); System.out.println("total " + total + " elements"); } }
> java Loop consume 9201 elements consume 66894 elements consume 100000 elements consume 98501 elements consume 77424 elements total 352020 elements
要素は100,000個しか用意しなかったにもかかわらず、352,020個の要素を取り出しています。
これは、コレクションが基本的にスレッドセーフでないために発生する問題です。
そこで、java.util.CollectionsクラスのsynchronizedList(List)というメソッドを使用すると、現在使用しているListをスレッドセーフにすることができます。
先ほどのプログラムのうち、mainメソッドの先頭 - Listを生成する部分で、使用しているListをスレッドセーフなものに変えてみます。
import java.util.Collections; import java.util.List; import java.util.LinkedList; public class SynchronizedLoop extends Thread { private final List list; private static int total = 0; private SynchronizedLoop(List list) { this.list = list; } public void run() { int count = 0; // consume while (list.remove(null)) { count++; } System.out.println("consume " + count + " elements"); synchronized (this.getClass()) { total += count; } } public static void main(String[] args) throws InterruptedException { // *** thread SAFE list *** List l = Collections.synchronizedList(new LinkedList()); // prepare 100,000 elements for (int i = 0; i < 100000; i++) l.add(null); Thread[] loops = new Thread[] { new SynchronizedLoop(l), new SynchronizedLoop(l), new SynchronizedLoop(l), new SynchronizedLoop(l), new SynchronizedLoop(l)}; // start 5 threads for (int i = 0; i < loops.length; i++) loops[i].start(); // wait for (int i = 0; i < loops.length; i++) loops[i].join(); System.out.println("total " + total + " elements"); } }
consume 19691 elements consume 19702 elements consume 21211 elements consume 19698 elements consume 19698 elements total 100000 elements
用意した要素の数と、取り出した要素の数が等しくなりました。
このほかにもjava.util.Collectionsには以下のようなメソッドがあります。