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

Revised: May/6th/2008: Since: Feb./23rd/2003

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

複数の要素からなるデータの集まりをコレクションと呼びます。コレクションは各々、リスト、セット、ツリー、ハッシュテーブルなどのデータ構造を持ちます。コレクション・フレームワークは J2SDK 1.2 で導入されたもので、JDK 1.2 未満で使われていたコレクション API の後継です。それまでの遅いコレクション・クラスを完全に新しくしたもので、インタフェースをベースに全体が設計されており、これを使うためには、共通のインタフェース・メソッドにのっとることになります。このような全体をフレームワークと呼びます。J2SDK 1.2 で、従来からあるコレクション・クラスも、フレームワークに含まれるものとして再設計されました。

参照:Sun Mycrosystemsのドキュメント

コレクション・フレームワークの概要

万能のアルゴリズム/データ構造はありません。要件に応じたコレクション・クラスを使いこなすためには、要件の明確化とそれぞれのクラスの特性を理解することが重要です。ここではJavaのコレクション・フレームワークの詳細は解説しませんが、全体を概観してみましょう。

コレクション・フレームワークは、3つのインタフェース、java.util.List, java.util.Set, java.util.Mapと、その実装クラスで構成されています。JDK 5.0 からは java.util.Queue が追加されました。

List, Set, Queue は Collection インタフェースを継承しています。List はシーケンシャル(直線的)なデータ構造を表します。Set は要素の重複を許さない集合を表します。Queue はキュー(待ち行列)を表します。Map はキーとデータの組からなるハッシュテーブルのデータ構造です。

一般に、プログラミングで用いるデータ構造は、次のいずれかに分類されることがほとんどです。

リスト List
配列。
スタック Stack
後入れ先出し(LIFO: Last In First Out)方式のデータ構造。先に入力されているものが次のものを自分の上に積んで、後で積まれたものの方が先に処理されて取り出されます。
キュー Queue
待ち行列。FIFO (First In First Out)方式のデータ構造。先に入れたものが先に出力され、後で入れたものは後で出力されます。オンライン・システムなどで、入力と処理に時間差がある場合に使われます。
ハッシュ Hash
ハッシュ・アルゴリズムに従うデータ構造。一般に、ハッシュ法とは、データ探索を高速にするために開発されたもので、格納データ自身から、その格納データ位置を生成する方法です。このデータ位置はハッシュ値と呼ばれ、ハッシュ値を求める処理をハッシュ関数と呼びます。ハッシュテーブルは、キーとデータの組からなるデータ構造で、キーからハッシュ値を求め、そのハッシュ値を索引としてデータを探索します。 ハッシュ関数によっては、複数の異なるキーから同じハッシュ値が求められることもあります。この状態をシノニム(同義語)と呼びます。この場合は、一つのハッシュ値に複数のデータが対応付けられることになり、同じハッシュ値内に含まれるデータの集まりのことをバケットと呼びます。

次の図は、コレクション・フレームワークのjava.utilパッケージにおける、代表的なインタフェース/クラスの階層図です。java.util.*のクラス/インタフェースと、java.util.concurrent.*のクラス/インタフェースが混じっています。

J2SE 5.0 コレクション・フレームワークのクラス図
図: J2SE 5.0 コレクション・フレームワークのクラス図

Vector, Stack, Hashtable は、Java 2以前から存在し、今でも広く使われています。それぞれ、可変長配列、スタック、ハッシュテーブルを実装します。Java 2では、これらの古いクラスも、コレクション・フレームワークの枠組みで改良されています。

古いコレクション・クラスには、処理速度が遅いという問題がありました。その最大の原因は、オブジェクトが常に「同期化」されることです。同期化とは、1つのオブジェクトが同時に最大でも1つのスレッドだけから更新されるように実装することです。データ整合性を確保するために、同期化の仕組みは不可欠である一方で、オブジェクトの生成/破棄と並び、Javaプログラムの実行速度を下げるものの代表格でもあります。

Java 2で導入されたコレクション・フレームワークのクラスは、同期化されないものとして設計されました。現在では、複数スレッド間で競合がない場合は、同期化されたコードによる無用なパフォーマンス上の劣化はほとんどありませんが、必要なときだけ同期化するという怠惰(lazy)な設計思想が踏襲されています。

コレクションフレームワークのインタフェース

以下、Java SE 6の各インタフェースの概要です。

java.util.Listインタフェース

要素は順序を持ち、索引によってアクセスされます。セットとは異なり、同じ値を重複して持つことが可能です。配列と同様、索引は0から始まります。add(), remove(), indexOf(), get()など、実装クラスで利用される殆どのメソッドを宣言しています。

ArrayListクラス
非同期。Vectorの後継。インタフェースLinkの基本的な実装クラスです。リサイズ可能な配列を実装します。
マルチスレッドのアクセスがある場合は同期化の考慮が必要です。エントリーの値が更新される場合は、タイミングとvolatileの問題として処理します。一方、エントリーの増減を伴うような場合は、インスタンス化した後で、Collections.synchronizedListメソッドを使用して「同期化されたリスト・オブジェクト」を生成します。当該リストをカプセル化するオブジェクトが既にある場合は、当該オブジェクトで同期化しても同じです。
LinkedListクラス
非同期。Stackの後継。二重リンクリストで、スタック/キューを実装します。要素順序を要素の前後2方向へのリンクで保持します。同期に関する注意点はArrayListと同じです。J2SE 5.0以上でインタフェースQueueの実装メソッドが、Java SE 6以上で、インタフェースDequeの実装メソッドが追加されました。
Vectorクラス
同期。リサイズ可能な配列を実装します。同期の要件が無ければArrayListLinkedListクラスを用いることが推奨されます。
Stackクラス
同期。後入れ先出し(FIFO)スタックを実装します。Vectorを継承し、push(), pop()などのメソッドが追加されています。Java SE 6.0以上では、Dequeインタフェースの実装クラスが推奨されています。

java.util.Mapインタフェース

キーと値の組を一つのエントリーとして持ちます。値へはキーによってアクセスされます。put(), get(), remove()などのメソッドを宣言しています。抽象クラスDictionaryの後継として作成されました。

HashMapクラス
非同期。Hashtableの後継。インタフェースMapの基本的な実装クラスです。ハッシュテーブルを実装します。
マルチスレッドのアクセスがある場合は同期化の考慮が必要です。エントリーの値が更新される場合は、タイミングとvolatileの問題として処理します。一方、インスタンス化した後で、Collections.synchronizedMapメソッドを使用して「同期化されたリスト・オブジェクト」を生成します。当該リストをカプセル化するオブジェクトが既にある場合は、当該オブジェクトで同期化しても同じです。
LinkedHashMapクラス
非同期。挿入順の二重リンクリストを実装し、各要素の前後2方向へのリンクで保持したハッシュテーブルを実装します。同期に関する注意点はHashMapと同じです。
TreeMapクラス
非同期。キーによってソートされたハッシュテーブルを実装します。順序は、自然順序付けかComparatorの実装クラスで指定します。大量なエントリを保持し、パフォーマンスが重要な場合は、TreeMapの利用を検討すべきです。同期に関する注意点はHashMapと同じです。
EnumMapクラス
非同期。Enumクラスをキーの持てるマップ。同期に関する注意点はHashMapと同じです。
WeakHashMapクラス
非同期。キーを弱参照オブジェクト(weak reference)で保持するハッシュテーブルを実装します。WeakHashMapオブジェクト自身は持続していても、キーを明示的に参照するスレッドがなくなると、当該キーが占有していたメモリ領域が再利用可能となります。内部管理上のオーバーヘッドと引き換えにメモリ効率が向上します。当該ハッシュから、そのキーのエントリーがなくなるので、持続的なデータの保持のためではなく、レジストリ、キャッシュのように利用します。同期に関する注意点はHashMapと同じです。
弱参照はjava.lang.ref.WeakReference型オブジェクトであり、オブジェクトを破棄してメモリを再利用するために内部的に実行されているガベッジ・コレクタを、間接的に操作する仕組みです。詳細は次のURLを参照してください:http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/ref/package-summary.html#reachability
Hashtableクラス
同期。キーと値の組からなるハッシュテーブルを実装します。抽象クラスDictionaryを継承します。同期の用件が無ければ、クラスHashMapを用いることが推奨されます。
Propertiesクラス
同期。Hashtableを継承します。A=B形式のプロパティの列挙、プロパティ・ファイルの読み込み/書き出しのために使います。ISO-8859-1 (Latin-1)以外の文字は、native2asciiツールなどを用いてUNICODEエスケープする必要があります。プロパティ・ファイルの読み書きには、java.util.ResourceBundlejava.util.prefs.Preferencesなどの利用も検討してください。

java.util.Setインタフェース

重複要素のない集合を表現します。このインタフェースの実装クラスは、内部的にはMapインタフェースの実装クラスに基づいており、動作特性も共通しています。

HashSetクラス
非同期。Setの基本的な実装クラスです。集合を実装します。
マルチスレッドのアクセスがある場合は同期化の考慮が必要です。エントリーの値が更新される場合は、タイミングとvolatileの問題として処理します。一方、インスタンス化した後で、Collections.synchronizedSetメソッドを使用して「同期化されたリスト・オブジェクト」を生成します。当該リストをカプセル化するオブジェクトが既にある場合は、当該オブジェクトで同期化しても同じです。
LinkedHashSetクラス
非同期。挿入順の二重リンクリストを実装し、要素の前後2方向へのリンクで保持した集合を実装します。同期に関する注意点はHashSetと同じです。
TreeSetクラス
非同期。順序を持つ集合を実装します。同期に関する注意点はHashSetと同じです。順序は、自然順序付けかComparatorの実装クラスで指定します。大量なエントリを保持し、パフォーマンスが重要な場合は、TreeSetの利用を検討すべきです。同期に関する注意点はHashSetと同じです。
EnumSetクラス
非同期。Enumクラスを持てるセット。同期に関する注意点はHashMapと同じです。

java.util.Queueインタフェース

待ち行列を表現します。最も基本的な実装クラスはLinkedListです。Comparatorで明示的に順序付けできるPriorityQueueや、スレッドセーフなConcurrentLinkedQueueなどの他、以下のサブインタフェースの実装クラスなど多数あります。

サブインタフェースjava.util.Dequeは、両端で要素を挿入/削除する機能を追加します。実装クラスには、LinkedListのほか、ArrayDeque, LinkedBlockingDequeがあります。

サブインタフェースjava.util.concurrent.BlockingQueueは、要素の追加時にキュー内に空きが生じるまで待機したり、要素の取得時にキュー要素が追加されるまで待機したりする機能を追加します。Dequeをスーパーインタフェースとするjava.util.concurrent.BlockingDequeインタフェースもあります。実装クラスには、ArrayBlockingQueue, LinkedBlockingQueue, LinkedBlockingDequeなどがあります。

全般にわたる注意

Java SE 6までのコレクション全般に渡る情報として、更に次のことを補足します:

  1. コレクション・クラスは要素をObject型で出し入れします。J2SE 5.0 (Tiger)以降は、ジェネリクスという言語仕様でインスタンス化時に「型パラメータ」を指定できるようになりました。型パラメタを使用してインスタンス化すれば、取り出した要素を使うために、目的の型へキャストする必要がなくなります。
  2. VectorHashtableなどの同期がとられるスレッド・セーフなオブジェクトはパフォーマンスが悪く、Java 2で追加されたコレクション・クラスでは同期がとられないようになっています。
  3. VectorHashtableなどの古いクラスでは、後方互換のために、Java 2以前のAPIを実装しています。また、要素を順番に取り出すために、EnumerationIteratorの両方が使えますが、新しいクラスではIteratorだけしか使えません。J2SE 5.0 (Tiger)以降では、コレクションクラスで型パラメータを指定した場合、イテレータでも型パラメータを指定しておくべきです。また、新しく導入されたfor/in構造を用いれば、イテレータは殆ど使わなくて済みます。
  4. クラスjava.util.Collectionsは、コレクションを操作するユーティリティ・クラスで、スレッド・セーフなオブジェクトや不変オブジェクトの生成などが可能です。J2SE 5.0で多数の便利なメソッドが追加されています。
  5. クラスjava.util.Arraysも、クラスCollectionsと同様、配列を操作するユーティリティ・クラスであり、検索、ソート、比較などが可能です。J2SE 5.0で多数の便利なメソッドが追加されています。


Copyright © 2003-2008 SUGAI, Manabu. All Rights Reserved.