コンテンツにスキップ

コレクションフレームワーク

プログラムを書いていると、多数のデータを扱う機会が多々あります。その場合に、全てデータ構造を自分で用意してもプログラムすることができますが、Javaにはもともとそのデータ構造を表現できるクラス群が用意されています。
これらのクラスはコレクションフレームワークと呼ばれ、うまく使用することによってプログラムを簡単に書くことができます。

コレクションフレームワークに含まれるクラス/インターフェースは次のようなものがあります。

クラス/インターフェース意味
java.util.List順序付きリストのインターフェース
java.util.ArrayList配列を用いたjava.util.Listの実装
java.util.LinkedListリンクリストを用いたjava.util.Listの実装
java.util.Set重複を許さない集合のインターフェース
java.util.HashSetハッシュを用いたjava.util.Setの実装
java.util.TreeSet二分探索木を用いたjava.util.Setの実装

また、コレクションに深い関わりのあるMapというデータ構造があります。
このデータ構造は、キーのペアを表現するためのもので、辞書のようなものです。
辞書は単語をキーにして意味や用例などのを取得することができます。

クラス/インターフェース意味
java.util.Mapキーと値のペアを表す構造のインターフェース
java.util.HashMapキーの検索にハッシュを用いたjava.util.Mapの実装
java.util.TreeMapキーの検索に二分探索木を用いたjava.util.Mapの実装

これらのクラスやインターフェースはJavaで元々用意されているもので、機能に不満があれば自分で機能を追加したクラスを作成することもできます。
しかし、ここに挙げたもので事足りるのであれば、わざわざ再開発しないで安全で高速なこれらのクラスを使用することをお勧めします。

java.util.Collectionはインターフェースです。
よって、そのままインスタンスを生成することができません。

import java.util.Collection;
public class C0S1 {
public static void main(String[] args) {
Collection c = new Collection();
}
}
Terminal window
> javac C0S1.java
C0S1.java:5: java.util.Collection abstract です。インスタンスを生成することはできません。
Collection c = new Collection();
^
エラー 1

そのため、Collectionインターフェースを実装するクラスを使用する必要があります。

import java.util.Collection;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.TreeSet;
public class C0S2 {
public static void main(String[] args) {
Collection c1 = new ArrayList();
Collection c2 = new LinkedList();
Collection c3 = new HashSet();
Collection c4 = new TreeSet();
System.out.println("c1 = " + c1.getClass());
System.out.println("c2 = " + c2.getClass());
System.out.println("c3 = " + c3.getClass());
System.out.println("c4 = " + c4.getClass());
}
}
Terminal window
> javac C0S2.java
> java C0S2
c1 = class java.util.ArrayList
c2 = class java.util.LinkedList
c3 = class java.util.HashSet
c4 = class java.util.TreeSet

注意すべき点は、java.util.Mapはコレクションを実装しません。

java.util.Collectionが持つメソッドのうち、特に使用されると思われるものを抜粋します。

コレクションにオブジェクトを追加する

import java.util.Collection;
import java.util.ArrayList;
public class C1S1 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("hoge");
c.add("foo");
c.add("bar");
System.out.println(c.toString());
}
}
Terminal window
> java C1S1
[hoge, foo, bar]

コレクションからオブジェクトを削除する

import java.util.Collection;
import java.util.ArrayList;
public class C1S2 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("hoge");
c.add("foo");
c.add("bar");
c.remove("foo");
System.out.println(c.toString());
}
}
Terminal window
> java C1S2
[hoge, bar]

コレクションに対象のオブジェクトが含まれているか検査する

import java.util.Collection;
import java.util.ArrayList;
public class C1S3 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("hoge");
c.add("foo");
System.out.println(c + " contains \"foo\" ? > " + c.contains("foo"));
System.out.println(c + " contains \"bar\" ? > " + c.contains("bar"));
}
}
Terminal window
> java C1S3
[hoge, foo] contains "foo" ? > true
[hoge, foo] contains "bar" ? > false

コレクションに格納されているオブジェクトの個数を取得する。

import java.util.Collection;
import java.util.ArrayList;
public class C1S4 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("hoge");
c.add("foo");
c.add("bar");
System.out.println(c.size());
}
}
Terminal window
> java C1S4
3

コレクションが空かどうか検査する。

import java.util.Collection;
import java.util.ArrayList;
public class C1S5 {
public static void main(String[] args) {
Collection c = new ArrayList();
System.out.println(c + " is Empty ? > " + c.isEmpty());
c.add("hoge");
c.add("foo");
c.add("bar");
System.out.println(c + " is Empty ? > " + c.isEmpty());
}
}
Terminal window
> java C1S5
[] is Empty ? > true
[hoge, foo, bar] is Empty ? > false

java.util.Listは順序つきのリストを表現するデータ構造で、次のような実装クラスを持ちます。

  • java.util.ArrayList
  • java.util.LinkedList
  • java.util.Vector

このコレクションは、主に可変長配列の一つの実現方法として使用する場面が多いと思われます。
また、java.util.List自体はインターフェースなので、使用する際は実装クラスをインスタンス化しましょう。

java.util.Listはjava.util.Collectionをスーパーインタフェースに持ちますが、List特有のメソッドがいくつか追加されています。

指定したインデックスにオブジェクトを挿入する。

import java.util.List;
import java.util.LinkedList;
public class C2S1 {
public static void main(String[] args) {
List l = new LinkedList();
l.add("hoge");
l.add("foo");
l.add("bar");
l.add(1, "*inserted*");
System.out.println(l.toString()));
}
}
Terminal window
> java C2S1
[hoge, *inserted*, foo, bar]

指定したインデックスのオブジェクトを取得する。

import java.util.List;
import java.util.ArrayList;
public class C2S2 {
public static void main(String[] args) {
List l = new ArrayList();
l.add("hoge");
l.add("foo");
l.add("bar");
System.out.println(l.get(1));
}
}
Terminal window
> java C2S2
foo

指定したインデックスにオブジェクトを格納する。

import java.util.List;
import java.util.ArrayList;
public class C2S3 {
public static void main(String[] args) {
List l = new ArrayList();
l.add("hoge");
l.add("foo");
l.add("bar");
l.set(1, "moge");
System.out.println(l.toString());
}
}
Terminal window
> java C2S3
[hoge, moge, bar]

指定したオブジェクトが格納されている先頭のインデックスを取得する。
指定したオブジェクトがリストの中に存在しない場合、-1を返す。

import java.util.List;
import java.util.ArrayList;
public class C2S4 {
public static void main(String[] args) {
List l = new ArrayList();
l.add("hoge");
l.add("foo");
l.add("bar");
System.out.println("index of foo = " + l.indexOf("foo"));
System.out.println("index of moge = " + l.indexOf("moge"));
}
}
Terminal window
> java C2S4
index of foo = 1
index of moge = -1

指定したオブジェクトが格納されている末尾のインデックスを取得する。
指定したオブジェクトがリストの中に存在しない場合、-1を返す。

import java.util.List;
import java.util.ArrayList;
public class C2S5 {
public static void main(String[] args) {
List l = new ArrayList();
l.add("hoge");
l.add("foo");
l.add("bar");
l.add("foo");
System.out.println("index of bar = " + l.indexOf("bar"));
System.out.println("last index of bar = " + l.lastIndexOf("bar"));
System.out.println("index of foo = " + l.indexOf("foo"));
System.out.println("last index of foo = " + l.lastIndexOf("foo"));
}
}
Terminal window
> java C2S5
index of bar = 2
last index of bar = 2
index of foo = 1
last index of foo = 3

リストの部分リストを取得する。
コピーではなくビューを返すので、部分リストに行った変更は元のリストにも反映される。

import java.util.List;
import java.util.ArrayList;
public class C2S6 {
public static void main(String[] args) {
List l = new ArrayList();
l.add("hoge");
l.add("foo");
l.add("bar");
l.add("hogehoge");
l.add("foofoo");
l.add("barbar");
List subl = l.subList(2, 5);
subl.set(0, "*replaced*");
System.out.println("list = " + l.toString());
System.out.println("sub list = " + subl.toString());
}
}
Terminal window
> java C2S6
list = [hoge, foo, *replaced*, hogehoge, foofoo, barbar]
sub list = [*replaced*, hogehoge, foofoo]
実装クラス使用する場面
java.util.ArrayList基本的にはこのクラスを使用する
java.util.LinkedList挿入や削除を頻繁に行う場合
java.util.Vectorほとんどの場面で使用しない

java.util.Vectorを使用しないのは、このクラスがレガシーコレクションと呼ばれる古い実装であるからです。
他の2個と違いVectorだけはスレッドセーフですが、それを理由にこのクラスの使用を考えるのはやめておいたほうが無難です。

Listの実装のうち、通常の配列を用いた実装で、特別重要なメソッドは存在しません。
ArrayList以外のクラスを使用する特別な理由がない限り、ArrayListを使用するのが無難です。

Listの実装のうち、リンクリストを用いた実装で、リンクリスト特有のメソッドがいくつか追加されています。
スタックやキューなどのデータ構造を表現したい場合は、LinkListを使用すると便利です。

リストの先頭/末尾にオブジェクトを追加する

import java.util.LinkedList;
public class C3S1 {
public static void main(String[] args) {
LinkedList l = new LinkedList();
l.add("hoge");
l.add("foo");
l.add("bar");
l.addFirst("*first*");
l.addLast("*last*");
System.out.println(l.toString());
}
}
Terminal window
> java C3S1
[*first*, hoge, foo, bar, *last*]

リストの先頭/末尾のオブジェクトを取得する。

import java.util.LinkedList;
public class C3S2 {
public static void main(String[] args) {
LinkedList l = new LinkedList();
l.add("hoge");
l.add("foo");
l.add("bar");
System.out.println(l.getFirst());
System.out.println(l.getLast());
}
}
Terminal window
> java C3S2
hoge
bar

リストの先頭/末尾のオブジェクトを削除し、削除したオブジェクトを取得する。

import java.util.LinkedList;
public class C3S3 {
public static void main(String[] args) {
LinkedList l = new LinkedList();
l.add("hoge");
l.add("foo");
l.add("bar");
l.add("hogehoge");
l.add("foofoo");
l.add("barbar");
System.out.println("first = " + l.removeFirst());
System.out.println("last = " + l.removeLast());
System.out.println("rest = " + l.toString());
}
}
Terminal window
> java C3S3
first = hoge
last = barbar
rest = [foo, bar, hogehoge, foofoo]
import java.util.LinkedList;
public class Stack {
private final LinkedList list;
public Stack() {
list = new LinkedList();
}
public void push(Object o) {
list.addFirst(o);
}
public Object pop() {
return list.removeFirst();
}
}
public class StackTest {
public static void main(String[] args) {
Stack stack = new Stack();
stack.push("hoge");
stack.push("foo");
stack.push("bar");
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
Terminal window
> java StackTest
bar
foo
hoge

Listは配列に近いデータ構造であるため、これらを相互に変換したい場合があります。

Listから配列に変換する場合は、java.util.ArraysのasList(Object[])メソッドを使用します。

import java.util.List;
import java.util.Arrays;
public class ArrayToList {
public static void main(String[] args) {
String[] array = {"hoge", "foo", "bar"};
List list = Arrays.asList(array);
System.out.println(list.toString());
}
}

配列からListに変換する場合は、java.util.ListのtoArray(Object[])メソッドを使用します。

import java.util.List;
import java.util.ArrayList;
public class ListToArray {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hoge");
list.add("foo");
list.add("bar");
String[] array = (String[]) list.toArray(new String[list.size()]);
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}

java.util.Setは集合を表現するデータ構造で、次のような実装クラスを持ちます。

  • java.util.HashSet
  • java.util.TreeSet

その他にも J2SDK 1.4 からいくつかの実装クラスが追加されています。

java.util.Setはjava.util.Collectionをスーパーインタフェースに持ち、特別重要なメソッドは追加されていません。

実装クラス使用する場面
java.util.HashSet順序を考慮しなくてよい場合
java.util.TreeSetjava.lang.Comparableを実装している、順序を持つオブジェクトを扱う場合

TreeSetの実装には2分探索木が使用されますので、データに順序関係のない場合は使用できませんし、データが元々整列している場合はパフォーマンスが低下します。

この実装では各クラスのhashCode()メソッドを呼び出し、その値を用いてデータの格納位置を決定させるため、hashCode()メソッドが正しく実装されている必要があります。
また、順序は保証されません。

import java.util.Set;
import java.util.HashSet;
public class C4S1 {
public static void main(String[] args) {
Set s = new HashSet();
s.add("hoge");
s.add("foo");
s.add("bar");
s.add("moge");
System.out.println(s.toString());
}
}
Terminal window
> java C4S1
[foo, bar, hoge, moge]

この実装ではjava.lang.ComparableインターフェースのcompareTo(Object o)メソッドを呼び出し、その結果を用いてデータの格納位置を決定させるため、java.lang.Comparableインターフェースを実装していないオブジェクトを扱えません。
また、この集合はcompareToメソッドで定義された順序どおりに整列されます。

import java.util.Set;
import java.util.TreeSet;
public class C4S2 {
public static void main(String[] args) {
Set s = new TreeSet();
s.add("hoge");
s.add("foo");
s.add("bar");
s.add("moge");
System.out.println(s.toString());
}
}
Terminal window
> java C4S2
[bar, foo, hoge, moge]

この場合では、java.lang.Stringの定める順序 (アルファベット順) で整列しました。

java.util.Mapはキーと値のペアを表現するデータ構造です。
MapはCollectionではありませんが、Collectionと深いかかわりがあります。
java.util.Map自体はインターフェースでインスタンス化できませんので、次のような実装クラスを使用します。

  • java.util.HashMap
  • java.util.TreeMap

java.util.Collectionが持つメソッドのうち、特に使用されると思われるものを抜粋します。

指定したキーと値のペアを追加する。

import java.util.Map;
import java.util.HashMap;
public class C5S1 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
System.out.println(m.toString());
}
}
Terminal window
> java C5S1
{foo=456, bar=789, hoge=123}

指定したキーに対応する値を取得する。
キーが登録されていない場合はnullを返す。

import java.util.Map;
import java.util.HashMap;
public class C5S2 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
System.out.println(m.get("hoge"));
System.out.println(m.get("foo"));
System.out.println(m.get("bar"));
System.out.println(m.get("moge"));
}
}
Terminal window
> java C5S2
123
456
789
null

指定したキーが存在するか検査する。

import java.util.Map;
import java.util.HashMap;
public class C5S3 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", null);
System.out.println("hoge => " + m.get("hoge") + " : " + m.containsKey("hoge"));
System.out.println("foo => " + m.get("foo") + " : " + m.containsKey("foo"));
System.out.println("bar => " + m.get("bar") + " : " + m.containsKey("bar"));
}
}
Terminal window
> java C5S3
hoge => 123 : true
foo => null : true
bar => null : false

指定したキーを持つエントリを削除する。

import java.util.Map;
import java.util.HashMap;
public class C5S4 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
m.remove("foo");
System.out.println(m.toString());
}
}
Terminal window
> java C5S4
{bar=789, hoge=123}

キーの集合を取得する。

import java.util.Map;
import java.util.Set;
import java.util.HashMap;
public class C5S5 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
Set s = m.keySet();
System.out.println(s.toString());
}
}
Terminal window
> java C5S5
[foo, bar, hoge]

エントリ (キーと値のペア1つ) の集合を取得する。

import java.util.Map;
import java.util.Set;
import java.util.HashMap;
public class C5S6 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
Set s = m.entrySet();
System.out.println(s.toString());
}
}
Terminal window
> java C5S6
[foo=456, bar=789, hoge=123]

値のグループを取得する。

import java.util.Map;
import java.util.Collection;
import java.util.HashMap;
public class C5S7 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
Collection c = m.values();
System.out.println(c.toString());
}
}
Terminal window
> java C5S7
[456, 789, 123]

マップが空かどうか検査する。

import java.util.Map;
import java.util.HashMap;
public class C5S8 {
public static void main(String[] args) {
Map m = new HashMap();
System.out.println(m + " is Empty ? > " + m.isEmpty());
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
System.out.println(m + " is Empty ? > " + m.isEmpty());
}
}
Terminal window
> java C5S8
{} is Empty ? > true
{foo=456, bar=789, hoge=123} is Empty ? > false

マップに格納されているエントリの個数を取得する。

import java.util.Map;
import java.util.HashMap;
public class C5S9 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
System.out.println(m.size());
}
}
Terminal window
> java C5S9
3

キーの検索にハッシュを使用するマップです。
この実装では各クラスのhashCode()メソッドを呼び出し、その値を用いてデータの格納位置を決定させるため、hashCode()メソッドが正しく実装されている必要があります。
また、順序は保証されません。

import java.util.Map;
import java.util.HashMap;
public class C6S1 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
m.put("moge", "abc");
System.out.println(m.toString());
}
}
Terminal window
> java C6S1
{foo=456, bar=789, hoge=123, moge=abc}

キーの検索に二分探索木を使用するマップです。
この実装ではjava.lang.ComparableインターフェースのcompareTo(Object o)メソッドを呼び出し、その結果を用いてデータの格納位置を決定させるため、java.lang.Comparableインターフェースを実装していないオブジェクトを扱えません。
また、この集合はcompareToメソッドで定義された順序どおりに整列されます。

import java.util.Map;
import java.util.TreeMap;
public class C6S2 {
public static void main(String[] args) {
Map m = new TreeMap();
m.put("hoge", "123");
m.put("foo", "456");
m.put("bar", "789");
m.put("moge", "abc");
System.out.println(m.toString());
}
}
Terminal window
> java C6S2
{bar=789, foo=456, hoge=123, moge=abc}

Collectionの中に含まれる各要素にアクセスする場合、java.util.Iteratorを使用することになります。

Collectionの中身すべてにアクセス

Section titled “Collectionの中身すべてにアクセス”

java.util.Collectionインターフェースには次のようなメソッドがあります。

::java.util.Iterator iterator()
:::コレクションの要素の反復子を取得する。

このメソッドを使用してIteratorを取得することにより、簡単にCollectionの各要素にアクセスできます。

java.util.Iteratorが持つメソッドのうち、特に重要な2つを挙げます。

::boolean hasNext()
:::次の要素が存在するか検査する。

::Object next()
:::次の要素を取得する。

この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);
}
}
}
Terminal window
> 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);
}
}
}
Terminal window
> 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());
}
}
Terminal window
> 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());
}
}
Terminal window
> 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());
}
}
Terminal window
> 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));
}
}
Terminal window
> 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);
}
}
Terminal window
> 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には以下のようなメソッドがあります。

  • List unmodifiableList(List)
  • Map unmodifiableMap(Map)
  • Set unmodifiableSet(Set)

次のプログラムを実行した結果を見てみます。

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");
}
}
Terminal window
> 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");
}
}
Terminal window
consume 19691 elements
consume 19702 elements
consume 21211 elements
consume 19698 elements
consume 19698 elements
total 100000 elements

用意した要素の数と、取り出した要素の数が等しくなりました。

このほかにもjava.util.Collectionsには以下のようなメソッドがあります。

  • Collection synchronizedCollection(Collection)
  • Set synchronizedSet(Set)
  • Map synchronizedMap(Map)