Powered by SmartDoc

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

コレクション・フレームワークとは何か

プログラミングでは、データを集め、集められたデータについて何らかの操作を行うことがよくあります。Javaには、こうしたことに対応するため、「コレクション・フレームワーク」という機能があります。「コレクション」とは、オブジェクトをまとめているオブジェクトです。

コレクション・フレームワークには、次のような機能があります。

  1. コレクション
  2. コレクションに含まれているそれぞれのオブジェクトに対して、繰り返しの処理をする
  3. ソートやサーチなどの代表的なアルゴリズムを使う

コレクション

コレクションには、6つの基本的なインターフェースと、その実装クラスがあります。

java.util.Collection
オブジェクトをまとめたもの。基本となるコレクション
java.util.List
オブジェクトが順番に並んだコレクション
java.util.Set
オブジェクトの重複がないコレクション
java.util.SortedSet
ある順序づけによってソートされているSet
java.util.Map
辞書のように、「キー」と「値」の組合せでオブジェクトが格納されているコレクション
java.util.SortedMap
キーがある順序づけによってソートされているMap

ここでは、List, Set, Mapの3つのインターフェースを見てみましょう。

java.util.List

Listとは何か

Listは、図2.1[List]のような、オブジェクトが順番に並んだコレクションです。

List

java.util.Listはインターフェースであり、これを実装したクラスには

などがあります。これらのクラスはそれぞれ実装方法が異なっており、状況に応じて使い分けることができます。

java.util.ArrayList

リスト4-1は、ファイルの内容を1行ずつArrayListクラスのインスタンスに格納するメソッドです。

List を使ったサンプル
// リスト 4-1

 1  public List<String> readFile(String fileName) {
 2      List<String> list = new ArrayList<String>();
 3      try {
 4          FileReader file_reader = new FileReader(fileName);
5 BufferedReader buffered_reader = new BufferedReader(file_reader);
 6          String line;
 7          while ((line = buffered_reader.readLine()) != null) {
 8              list.add(line);
 9          }
10          buffered_reader.close();
11          file_reader.close();
12      } catch (FileNotFoundException e) {
13          e.printStackTrace();
14      } catch (IOException e) {
15          e.printStackTrace();
16      }
17      return list;
18  }

リスト4-1では、2行目で

        List<String> list = new ArrayList<String>();

のように、ArrayListクラスのインスタンスを生成しています。このreadFileメソッドでは、ファイルの内容を1行ずつ取りだしています。取り出したデータは、8行目でListインターフェースのメソッドList#add(String)を使い、オブジェクトlistに追加します。つまりreadFileメソッドでは、ファイルのすべての行をlistに格納し、最後にこのlistを返しているのです。

インタフェースの利用

リスト4-1の2行目について、もう少し解説を加えましょう。

        List<String> list = new ArrayList<String>();

普通なら、次のように書くところです。

        ArrayList<String> list = new ArrayList<String>();

しかし、先に述べたように、ArrayListはListインタフェースを実装しています。

public class ArrayList implements List {
		……
}

この場合、ArrayList型のインスタンスは、List型も持っていることになります。よって、次のように記述できます。

        List<String> list = new ArrayList<String>();

では、ArrayListの代わりに後述するLinkedListを使うようにしたとしましょう。ArrayListと同様に、LinkedListもListインタフェースを実装しているので、次のように書けばよいのです。

        List<String> list = new LinkedList<String>();

Generics

ArrayListを使ったプログラムをJ2SE 1.4で書くと、次のようなパターンになります。

	List l = new ArrayList();
	l.add("1000");
	……
	String str = (String)list.get(0);

addメソッドではString型である"1000"というオブジェクトを追加しています。このメソッドの引数はObject型です。JavaのすべてのクラスはObject型を継承しているので、つまりaddメソッドにはどのような型のオブジェクトでも追加できるということになります。

最後の行のように、getメソッドでオブジェクトを取得しています。このメソッドの返値もObject型なので、(String)のようにキャストを行っています。

ところが、この方式では、listインスタンスにどのような型のオブジェクトでも追加できてしまいます。そのため、String型以外のオブジェクトがlistに追加されていると、最終行のキャストのところでエラーになってしまいます。

このエラーは、コンパイル時ではなく、実行時に検出されることになります。実行時エラーの原因追及は、なかなかやっかいな作業です。

こうしたエラーを防ぐために、J2SE 5.0からはGenericsという機能が追加されました。

Genericsを使うと、次のようにプログラムを記述できます。

	List<String> list = new ArrayList<String>();
      list.add("1000");
	……
	String str = list.get(0);

1行目の2カ所で登場する"<String>"の部分がGenericsです。この行では、Listで管理するオブジェクトをString型に限定しています。それ以外の型のオブジェクトが追加されると、エラーになります。つまり、listインスタンスに含まれるのはString型だけであることが保証されるのです。このおかげで、最終行のgetメソッドではキャストが必要なくなります。

Genericsを使うことによって、コレクションの定義が明確になり、実行時エラーが起こる可能性をかなり減らすことができます。

java.util.LinkedList

Listインターフェースを実装したクラスのなかでは、java.util.ArrayListがよく使われています。しかし、ArrayListのインスタンスに対して、頻繁にオブジェクトの挿入や削除を行うなら、ArrayListではなくjava.util.LinkedListを使った方が良いでしょう。このクラスは図2.2[LinkedList]のように「ダブルリンクトリスト」という手法を使って実装されており、オブジェクトの挿入や削除が頻繁に起こる場合、効率が良くなるからです。

LinkedList

リスト4-1でjava.util.ArrayListをjava.util.LinkedListに変えるには、2行目の部分を

        List<String> list = new LinkedList<String>();

とするだけです。他の部分は変更しなくても構いません。

同期化

Javaはマルチスレッドに対応したプログラミング言語なので、複数のスレッドを同時に動作させることが可能になります。

1つのオブジェクトに、2つのスレッドがアクセスするとしましょう。1つのスレッドでは、そのオブジェクトを削除しようとしています。別のスレッドでは、オブジェクトを内容を取得しようとしています。この2つのスレッドが同時に実行され、オブジェクトへのアクセスが同時に行われた場合、矛盾が生じる可能性があります。

そこで、ひとつのオブジェクトに、同時にはアクセスできないようにする工夫が必要です。これを「同期化」と言います。ただし、同期化させるとプログラムの処理が遅くなります。

ArrayListやLinkedListは同期化されていないので、マルチスレッドで使うときには動作が保証されません。同期化するには、リスト4-2のようにjava.util.CollectionsクラスのメソッドCollections#synchronizedList(List)を使います。

    // リスト 4-2
    List<String> newList = Collections.synchronizedList(list);

java.util.Vectorは、Java 2 SDKが登場する前から存在しており、ArrayListによく似た機能を持っています。ArrayListとは違って、同期化もされています。Java 2 SDK以降でのVectorは、java.util.Listインターフェースを実装するように変更されました。

java.util.Set

Setは、オブジェクトの重複がないコレクションです。数学での「集合」をモデル化しています。

Set

java.util.Setはインターフェースであり、これを実装したクラスに

があります。

リスト4-3は、HashSetクラスのインスタンスにあらかじめオブジェクトを登録しておき、指定したオブジェクトがコレクションに存在するかどうか確かめるプログラムです。

SetTest.java
// リスト 4-3

 1  import java.util.Set;
 2  import java.util.HashSet;
 3
 4  public class SetTest {
 5      private Set<String> set;
 6
 7      public SetTest() {
 8          set = new HashSet<String>();
 9
10          // あらかじめデータを登録する
11          set.add("10000");
12          set.add("20000");
13          set.add("30000");
14      }
15
16      public void check(String number) {
17          if (set.contains(number)) {
18              System.out.print(number);
19              System.out.println("はあります");
20          } else {
21              System.out.print(number);
22              System.out.println("はありません");
23          }
24      }
25
26      public static void main(String[] argv) {
27          SetTest test = new SetTest();
28          test.check("10000");
29          test.check("10001");
30          test.check("20000");
31      }
32  }
// リスト 4-3 の実行例
C:\Home> java SetTest
10000はあります
10001はありません
20000はあります

8行目のコンストラクタで生成されるインスタンスの型は、java.util.Setです。11行目から13行目でオブジェクトを登録し、17行目では引数で指定したオブジェクトが存在するかどうか確かめています。

ArrayListと同じように、HashSetも同期化されていないので、マルチスレッドで使うときには動作が保証されません。同期化するには、リスト4-4のように、java.util.CollectionsクラスのメソッドCollections#synchronizedSet(Set)を使います。

    // リスト 4-4
    Set<String> newSet = Collections.synchronizedSet(set);

java.util.Setを継承しているインターフェースに、java.util.SortedSetがあります。java.util.SortedSetは、内部でオブジェクトの順番を保持することを保証します。つまりコレクションにオブジェクトの追加や削除を行っても、その中のオブジェクトは自動的に並びかえられます。

java.util.SortedSetを実装しているクラスには、java.util.TreeSetがあります。

java.util.Map

Mapは、「キー」と「値」の組合せでオブジェクトが格納されるコレクションです。ちょうど辞書のようなイメージになります。

Map

java.util.Mapはインターフェースであり、これを実装したクラスには

などがあります。これらのクラスはそれぞれ実装方法が異なっており、状況に応じて使い分けることができます。

リスト4-5は、HashMapクラスのインスタンスにあらかじめオブジェクトを登録しておき、指定したキーがコレクションに存在するかどうか確かめ、キーが存在するならそのキーに対応する値を出力するプログラムです。

MapTest.java
// リスト 4-5

 1  import java.util.Map;
 2  import java.util.HashMap;
 3
 4  public class MapTest {
 5
 6      private Map<String, String> map;
 7
 8      public MapTest() {
 9           map = new HashMap<String, String>();
10          map.put("10000", "福沢諭吉");
11          map.put("5000",  "樋口一葉");
12          map.put("2000",  "紫式部");
13          map.put("1000",  "野口英世");
14      }
15  
16      public void check(String key) {
17          if (map.containsKey(key)) {
18              String value = map.get(key);
19              System.out.print(key);
20              System.out.print("円札は");
21              System.out.print(value);
22              System.out.println("です");
23          } else {
24              System.out.print(key);
25              System.out.println("円札はありません");
26          }
27      }
28  
29      public static void main(String[] argv) {
30          MapTest test = new MapTest();
31          test.check("500");
32          test.check("2000");
33          test.check("10000");
34      }
35  }
// リスト 4-5 の実行例
C:\Home> java MapTest
500円札はありません
2000円札は紫式部です
10000円札は福沢諭吉です

9行目のコンストラクタで生成されるインスタンスの型は、java.util.Mapにしています。10行目から13行目でキーと値のペアを登録し、17行目では引数で指定したキーが存在するかどうか確かめています。そして18行目で引数をキーにして値を取り出します。

HashMapもやはり同期化されていないので、マルチスレッドで使うときには動作が保証されません。同期化するには、リスト4-6のようにjava.util.CollectionsクラスのメソッドCollections#synchronizedMap(Map)を使います。

    // リスト 4-6
    Map<String, String> newMap = Collections.synchronizedMap(map);

java.util.Mapを継承しているインターフェースに、java.util.SortedMapがあります。java.util.SortedMapは、内部でキーの順番を保持することを保証しています。つまり、コレクションにオブジェクトの追加や削除を行っても、その中のオブジェクトはキーの順序で自動的に並びかえられます。java.util.SortedMapを実装しているクラスには、java.util.TreeMapがあります。

java.util.Hashtableは、Java 2 SDKが登場する前から存在しており、HashMapによく似た機能を持っています。HashMapとは違って、同期化もされています。Java 2 SDK以降でのHashtableは、java.util.Mapインターフェースを実装するように変更されました。

コレクション中のオブジェクトの繰り返し処理

java.util.Iterator

コレクションに含まれているそれぞれのオブジェクトに対して、繰り返しの処理をするには、java.util.Iteratorを使います。図2.5[Iterator]のようなイメージになります。

Iterator

まずは、Iteratorを使ったサンプルを見てみましょう。リスト4-7は、リスト4-1のプログラムに、List型のオブジェクトに格納されたファイルの中身をIteratorを使って出力する機能を加えたものです。

IteratorTest.java
// リスト 4-7

 1  import java.util.List;
 2  import java.util.ArrayList;
 3  import java.util.Iterator;
 4  import java.io.FileReader;
 5  import java.io.BufferedReader;
 6  import java.io.FileNotFoundException;
 7  import java.io.IOException;
 8  
 9  public class IteratorTest {
10  
11      public List<String> readFile(String fileName) {
12          List<String> list = new ArrayList<String>();
13          try {
14              FileReader file_reader = new FileReader(fileName);
15 BufferedReader buffered_reader = new BufferedReader(file_reader);
16              String line;
17              while ((line = buffered_reader.readLine()) != null) {
18                  list.add(line);
19              }
20          } catch (FileNotFoundException fnfe) {
21              fnfe.printStackTrace();
22          } catch (IOException ioe) {
23              ioe.printStackTrace();
24          }
25          return list;
26      }
27  
28      public static void main(String[] argv) {
29          IteratorTest test = new IteratorTest();
30          List<String> list = test.readFile(argv[0]);
31          Iterator<String> iterator = list.iterator();
32          while (iterator.hasNext()) {
33              String value = iterator.next();
34              System.out.println(value);
35          }
36      }
37  }
// リスト 4-7 の実行例
C:\Home> java IteratorTest sample.txt
java.util.List
java.util.Set
java.util.Map
java.util.SortedSet
java.util.SortedMap
java.util.Date
java.text.DateFormat
org.w3c.dom.Node
gnu.regexp.RE
jp.ac.wakhok.tomoharu.CSVLine

まず31行目でIteratorを取り出します。32行目でまだ処理をしていないオブジェクトが残っているかどうかチェックし、33行目でオブジェクトを1つ取り出しています。

このようにIteratorを使えば、繰り返しのループ処理を簡潔に書けるのです。

また、Iteratorとよく似た機能を持つ、java.util.Enumerationがあります。JDK1.1まではjava.util.Iteratorはなかったので、VectorやHashtableに含まれているオブジェクトの繰り返し処理に使われます。

for文の拡張

J2SE 5.0からは、for文を使ってコレクションを操作することも容易になりました。

次のようなパターンで、コレクションや配列に含まれる要素を繰り返し処理することができます。

for (要素の型 要素名: コレクション or 配列) {
	……
}

先のIteratorの例は、for文を使って次のようにかけます。Iteratorを使うよりもプログラムが単純になっていることがわかりますね。

for (String value: list) {
	System.out.println(value);
}

代表的なアルゴリズムを使う

コレクション・フレームワークでは、ソートやサーチなどの代表的なアルゴリズムを使ってコレクションを操作できます。このためのクラスとして、

の2つがあります。いずれも、ソートやサーチなどの便利な機能をまとめたものです。java.util.Collectionsはコレクションに対して使い、java.util.Arraysは配列に対して使うものです。

リスト4-7の出力結果を、アルファベット順に並べてみましょう。そのためには、リスト4-7のmainメソッドを、リスト4-8のように変更します。

IteratorAndSortTest.java の一部
// リスト 4-8

 1      public static void main(String[] argv) {
 2          IteratorAndSortTest test = new IteratorAndSortTest();
 3          List<String> list = test.readFile(argv[0]);
 4          Collections.sort(list);
 5          Iterator<String> iterator = list.iterator();
 6          while (iterator.hasNext()) {
 7              String value = iterator.next();
 8              System.out.println(value);
 9          }
10      }
// リスト 4-8 の実行例
C:\Home> java IteratorAndSortTest sample.txt
gnu.regexp.RE
java.text.DateFormat
java.util.Date
java.util.List
java.util.Map
java.util.Set
java.util.SortedMap
java.util.SortedSet
jp.ac.wakhok.tomoharu.CSVLine
org.w3c.dom.Node

リスト4-7とリスト4-8のmainメソッドの違いは、リスト4-8の4行目の部分だけです。ここでは、List型のオブジェクトlistを「自然な順序付け」に従ってソートします。この場合は、アルファベット順にソートしています。ソートの規則を細かく設定する方法もありますが、ここでは省略します。