初出 JAVA PRESS Vol.26

Java API ダイジェスト

java.util.Iterator

Iteratorインタフェースはコレクションの要素を先頭から1つづつ、シーケンシャルにアクセスするためのインタフェースです。GoFのデザインパターンのイテレータパターンに対応しており、その特徴は

ことにあります。このため、Iteratorインタフェースをインプリメントしたクラスやその要素数、要素の保持方法などによらずに統一したインタフェースで要素にアクセスすることができます。

Iteratorインタフェースなので、そのまま生成することはできません。生成にはCollectionインタフェースのiteratorメソッドを使用します。

CollectionインタフェースはListインタフェースやSetインタフェースのスーパーインタフェースなので、これらのインタフェースでも使用することができます。


コレクションのシーケンシャルアクセス

Iteratorインタフェースはコレクションの要素に対してシーケンシャルにアクセスするための最小限のメソッドが定義されています。

hasNextメソッドはアクセスしていない残りの要素があるかを調べるメソッドです。残りの要素があれば、nextメソッドで次の要素を取得できます。

■ コレクションへのアクセス

nextメソッドで最後にアクセスした要素を削除するにはremoveメソッドを使用します。要素を削除すると、オリジナルのコレクションの要素も削除されてしまうので、注意が必要です。

■ コレクション要素の削除

イテレータパターン

■ コレクションのシーケンシャルアクセス

public boolean hasNext()

要素を1つづつアクセスしているときに、まだアクセスしていない要素があればtrueを返します。すべての要素をアクセスしてしまった場合、falseが返ります。

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インタフェースを使用しています。

リスト 1 シーケンシャルアクセスの例
    Iterator iterator = collection.iterator();
    while (iterator.hasNext()) {
        A a = (A)iterator.next();    // A にキャストが必要
        a.foo();
    }

■ コレクション要素の削除

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に登録した例を示しています。

リスト 2 Mapオブジェクトに対するシーケンシャルアクセスの例
    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));
        }
    }

 

リスト 3 SelectorクラスにおけるIteratorインタフェースの使用例
        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)