イテレータパターン
■ コレクションのシーケンシャルアクセス
- hasNext
public boolean hasNext()
要素を1つづつアクセスしているときに、まだアクセスしていない要素があればtrueを返します。すべての要素をアクセスしてしまった場合、falseが返ります。
- next
public Object next()
まだアクセスを行っていない次の要素を返します。戻り値はObjectクラスなので、実行時には適切なクラスへのキャストが必要です。
アクセスできる要素がない時に (hasNext メソッドの戻り値が false の時に)、nextメソッドをコールするとjava.util.NoSuchElementExceptionが発生します。
リスト1は要素がAクラスのコレクションに対して、シーケンシャルにアクセスする例です。
SetインタフェースやMapインタフェースは順序がないため、インデックスを使用したアクセスを行うことができません。しかし、Iteratorインタフェースを使用することでシーケンシャルアクセスを行うことができます。Mapオブジェクトからは直接Iteratorオブジェクトを取り出すことはできませんが、keySetメソッドやvaluesメソッドで得られたSetオブジェクトからIteratorオブジェクトを取得できます。リスト2はMapオブジェクトの全要素を表示するメソッドです。ここでは、MapオブジェクトのキーをアクセスするためにIteratorインタフェースを使用しています。
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
A a = (A)iterator.next(); // A にキャストが必要
a.foo();
}
■ コレクション要素の削除
- remove
public void remove()
nextメソッドで取得できた要素を削除します。Iteratorオブジェクトはオリジナルのコレクションをコピーするわけではなく、オリジナルのコレクションに対してシーケンシャルアクセスを行っているだけです。このため、removeメソッドで要素を削除すると、オリジナルのコレクションの要素も削除されるので、使用するには注意が必要です。
イディオム的にIteratorインタフェースが使われる例として、New I/Oでノンブロッキングモード通信に使用されるjava.nio.channels.Selectorクラスがあります。Selectorクラスでは予めノンブロッキングモード行う入出力処理を登録しておきます。
登録した入出力処理が発生すると、発生した全ての入出力処理がSetオブジェクトとして取得できるので、Iteratorを使用してシーケンシャルアクセス行います。発生した入出力処理に対応するときには、それをremoveメソッドで削除しておきます。削除を行わないと、処理が行われていないと認識されてしまいます。詳しくはJavaPress Vol.24のNew I/Oの解説をご覧ください。
リスト3はイディオム的に使われる部分を示しています (完全なプログラムにはなっていません)。selectorが Selectorオブジェクトになります。ここではソケットのアクセプトと受信をselectorに登録した例を示しています。
public void showMap(Map map) {
// Iterator を Set を介して取得
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while (it.hasNext()) {
Object key = it.next();
System.out.println("Key: " + key + " Value: " + map.get(key));
}
}
while (selector.select() > 0) {
// 発生した動作の一覧を Iterator で扱う
Iterator keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = (SelectionKey)keyIterator.next();
keyIterator.remove(); // 処理する動作を削除しておく
if (key.isAcceptable()) {
// accept の処理
...
} else if (key.isReadable()) {
// データの受信
...
}
}
}
参考文献: 「オブジェクト指向における再利用のための デザインパターン」, Erich Gamma 他, ソフトバンク, ISBN 4-7973-1112-6
コラム ListインタフェースとIteratorインタフェース
ListインタフェースはIteratorインタフェースを使用しなくともシーケンシャルアクセスが可能です。では、Listインタフェースに対してIteratorインタフェースは使う意味がないのでしょうか。
ArrayListクラスは直接使用したほうがIteratorインタフェースを使うより高速にシーケンシャルアクセスができます。しかし、意外なことにLinkedListクラスをそのまま使用したシーケンシャルアクセスは、Iteratorインタフェースを使った場合より低速になります。
つまり、Listインタフェースを実装しているクラスによってシーケンシャルアクセスのパフォーマンスが異なるのです。したがって、実装しているクラスが特定できないような場合には、Iteratorインタフェースを使用するのが無難です。これは実装に関わらず統一した方法でシーケンシャルアクセスを行うというIteratorの本来の目的からも望ましいといえます。
(2002.09)