イテレータパターン
■ コレクションのシーケンシャルアクセス
- 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)