1. 概要

このチュートリアルでは、JavaでのIterableおよびIteratorインターフェースの使用法とそれらの違いを見ていきます。

2. Iterableインターフェース

Iterable インターフェースは、java.langパッケージに属しています。 反復可能なデータ構造を表します。

Iterable インターフェースは、Iteratorを生成するメソッドを提供します。 Iterable を使用する場合、インデックスで要素を取得することはできません。 同様に、データ構造から最初または最後の要素を取得することもできません。

Javaのすべてのコレクションは、Iterableインターフェースを実装しています。

2.1. Iterableを反復処理します

for-eachループとも呼ばれる拡張されたforループを使用して、コレクション内の要素を反復処理できます。 ただし、このようなステートメント内で使用できるのは、Iterableインターフェイスを実装するオブジェクトのみです。 whileステートメントをIteratorと組み合わせて使用して、要素を反復処理することもできます。

for -eachステートメントを使用して、Listの要素を反復処理する例を見てみましょう。

List<Integer> numbers = getNumbers();
for (Integer number : numbers) {
    System.out.println(number);
}

同様に、 forEach()メソッドをラムダ式と組み合わせて使用できます。

List<Integer> numbers = getNumbers();
numbers.forEach(System.out::println);

2.2. Iterableインターフェースの実装

Iterable インターフェースのカスタム実装は、反復したいカスタムデータ構造がある場合に便利です。

配列内の要素を保持するショッピングカートを表すクラスを作成することから始めましょう。 forとは呼びません-各ループは配列上で直接行われます。 代わりに、Iterableインターフェースを実装します。 クライアントが選択したデータ構造に依存することは望ましくありません。 クライアントに反復機能を提供すると、クライアントがコードを変更しなくても、別のデータ構造を簡単に使用できます。

ShoppingCart クラスは、 Iterable インターフェイスを実装し、その iterate()メソッドをオーバーライドします。

public class ShoppingCart<E> implements Iterable<E> {

    private E[] elementData;
    private int size;

    public void add(E element) {
        ensureCapacity(size + 1);
        elementData[size++] = element;
    }

    @Override
    public Iterator<E> iterator() {
        return new ShoppingCartIterator();
    }
}

add()メソッドは、要素を配列に格納します。 配列のサイズと容量は固定されているため、 ensureCapacity()メソッドを使用して要素の最大数を拡張します。

カスタムデータ構造でiterator()メソッドを呼び出すたびに、Iteratorの新しいインスタンスが生成されます。 イテレータが現在の反復状態を維持する責任があるため、新しいインスタンスを作成します。

iterator()メソッドの具体的な実装を提供することにより、拡張された for ステートメントを使用して、実装されたクラスのオブジェクトを反復処理できます。

次に、カスタムイテレータを表すShoppingCartクラス内に内部クラスを作成しましょう。

public class ShoppingCartIterator implements Iterator<E> {
    int cursor;
    int lastReturned = -1;

    public boolean hasNext() {
        return cursor != size;
    }

    public E next() {
        return getNextElement();
    }

    private E getNextElement() {
        int current = cursor;
        exist(current);

        E[] elements = ShoppingCart.this.elementData;
        validate(elements, current);

        cursor = current + 1;
        lastReturned = current;
        return elements[lastReturned];
    }
}

最後に、反復可能なクラスのインスタンスを作成し、それを拡張されたforループで使用しましょう。

ShoppingCart<Product> shoppingCart  = new ShoppingCart<>();

shoppingCart.add(new Product("Tuna", 42));
shoppingCart.add(new Product("Eggplant", 65));
shoppingCart.add(new Product("Salad", 45));
shoppingCart.add(new Product("Banana", 29));
 
for (Product product : shoppingCart) {
   System.out.println(product.getName());
}

3. イテレータインターフェイス

Iterator は、Javaコレクションフレームワークのメンバーです。 java.utilパッケージに属しています。 このインターフェイスを使用すると、反復中にコレクションから要素を取得または削除できます。

さらに、データ構造を反復処理してその要素を取得するのに役立つ2つのメソッド、 next() hasNext()があります。

さらに、 remove()メソッドがあり、Iteratorが指す現在の要素を削除します。

最後に、 forEachRemaining(Consumer <? スーパーE>アクション) メソッドは、データ構造内の残りの要素ごとに指定されたアクションを実行します。

3.1. コレクションを繰り返し処理します

Integer要素のListを反復処理する方法を見てみましょう。 この例では、 while ループと、メソッド hasNext()および next()を組み合わせます。

ListインターフェイスはCollectionの一部であるため、Iterableインターフェイスを拡張します。 コレクションからイテレータを取得するには、 iterator()メソッドを呼び出すだけです。

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(40);

Iterator<Integer> iterator = numbers.iterator();

さらに、 hasNext()メソッドを呼び出すことにより、イテレーターに残りの要素があるかどうかを確認できます。 その後、 next()メソッドを呼び出すことで要素を取得できます。

while (iterator.hasNext()) {
   System.out.println(iterator.next());
}

next()メソッドは、反復から次の要素を返します。 一方、そのような要素がない場合は、NoSuchElementExceptionをスローします。

3.2. イテレータインターフェースの実装

次に、Iteratorインターフェースを実装します。 カスタム実装は、条件付き要素の取得を使用してコレクションを反復処理する必要がある場合に役立ちます。 たとえば、奇数または偶数を反復処理するためにカスタムイテレータを使用できます。

説明のために、指定されたコレクションから素数を繰り返し処理します。 ご存知のように、数が1つだけで割り切れる場合、その数は素数と見なされます。

まず、数値要素のコレクションを含むクラスを作成しましょう。

class Numbers {
 
    private static final List<Integer> NUMBER_LIST =
      Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}

さらに、Iteratorインターフェースの具体的な実装を定義しましょう。

private static class PrimeIterator implements Iterator<Integer> {

    private int cursor;

    @Override
    public Integer next() {
        exist(cursor);
        return NUMBER_LIST.get(cursor++);
    }

    @Override
    public boolean hasNext() {
        if (cursor > NUMBER_LIST.size()) {
            return false;
        }

        for (int i = cursor; i < NUMBER_LIST.size(); i++) {
            if (isPrime(NUMBER_LIST.get(i))) {
                cursor = i;
                return true;
            }
        }

        return false;
    }
}

具体的な実装は通常、内部クラスとして作成されます。 さらに、現在の反復状態を維持する責任があります。 上記の例では、インスタンス変数内に次の素数の現在の位置を格納しました。 next()メソッドを呼び出すたびに、変数には次の素数のインデックスが含まれます。

next()メソッドの実装では、要素が残っていない場合にNoSuchElementException例外をスローする必要があります。そうしないと、反復によって予期しない動作が発生する可能性があります。

PrimeIteratorクラスの新しいインスタンスを返すNumberクラス内のメソッドを定義しましょう。

public static Iterator<Integer> iterator() {
    return new PrimeIterator();
}

最後に、whileステートメント内でカスタムイテレータを使用できます。

Iterator<Integer> iterator = Numbers.iterator();
 
while (iterator.hasNext()) {
   System.out.println(iterator.next());
}

4. IterableIteratorの違い

要約すると、次の表は、IterableインターフェイスとIteratorインターフェイスの主な違いを示しています。

反復可能 イテレータ
forを使用して繰り返すことができるコレクションを表します-各ループ コレクションを反復処理するために使用できるインターフェースを表します
Iterable を実装する場合、 iterator()メソッドをオーバーライドする必要があります Iterator を実装する場合、 hasNext()および next()メソッドをオーバーライドする必要があります
反復状態を保存しません 反復状態を格納します
反復中に要素を削除することは許可されていません 反復中に要素を削除できます

5. 結論

この記事では、JavaのIterableインターフェイスとIteratorインターフェイスの違いとその使用法について説明しました。

いつものように、例のソースコードはGitHubから入手できます。