Help us understand the problem. What is going on with this article?

[Java] Iteratorのメモ

More than 3 years have passed since last update.

目的

  • Iterator の使いどころの整理
  • 類似の ListIterator についても簡単に
  • ついでに古いコレクションフレームワークの Enumeration も少しだけ

java.util.Iterator について

コレクションなどの要素を順番に処理する場合に使用します。
といっても、Java5.0 でサポートされた拡張for文があるため、Iteratorを明示的に使用することはありまり多くありませんが。Java8 ではストリーム関係が追加されて、さらに使われることが少なくなりそうです。

しかし場合によっては Iterator を明示的に使用しないといけないケースはあると思います。

メソッド

簡単に。

  • Iterator#hasNext() : 次のデータがある場合に真
  • Iterator#next() : 次のデータを取得
  • Iterator#remove() : 読み込んだデータを削除 (オプション操作)
  • Iterator#forEachRemaining() : 各要素を順番に処理する(Java8で追加)

remove() はオプション操作なので、扱う Iterator によっては削除ができない場合があります(例えば Collections.unmodifiableList() は変更不能なリストを返すので、そこから取得した Iterator では削除ができません)。
ちなみに、remove() は Java8 からデフォルトメソッドになりました。
デフォルト実装は UnsupportedOperationException を投げるだけのものです。

また、Java8 で追加された forEachRemaining() もデフォルトメソッドです。

要素を削除する

明示的に Iterator を扱わないといけないケースは、要素を削除したい場合がほとんどだと思います。

不要な要素、あるいは処理済みになった要素を、コレクションから削除する場合には、以下のようにすると思います。

List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));

Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
    int num = iterator.next();
    if (num == 0) {
        // 0 は削除
        iterator.remove();
    }
}

System.out.println(list); // -> [3, 1, 4, 1, 5, 9, 2, 6]

remove() は前回 next() が返した要素を削除します。
そのため next() を呼び出していない場合や、既に remove() を呼び出している場合には、呼び出すことができません(例外が投げられます)。

java.util.ListIterator について

Iterator よりも影の薄いものに ListIterator があります。
名前のとおり、リスト用に拡張されたイテレータです。

Iterator と違うのは、逆方向にも移動できることと、要素の追加や置き換えができることです。
また、Iterator は先頭からしか取得できませんが、 ListIterator はリストの任意の位置から取得することができます。

メソッド

  • ListIterator#hasNext() : 次のデータがある場合に真
  • ListIterator#next() : 次のデータを取得
  • ListIterator#hasPrevious() : 前のデータがある場合に真
  • ListIterator#previous() : 前のデータを取得
  • ListIterator#remove() : 読み込んだデータを削除 (オプション操作)
  • ListIterator#forEachRemaining() : 各要素を順番に処理する(Java8で追加)
  • ListIterator#add() : next() で返ってくる要素の手前にデータを追加 (オプション操作)
  • ListIterator#set() : 読み込んだデータを置き換え (オプション操作)
  • ListIterator#nextIndex() : next() で返ってくる要素のインデックス(末尾の場合には、リストのサイズ)
  • ListIterator#previousIndex() : previous() で返ってくる要素のインデックス(先頭の場合には、-1)

ListIterator の取得

ListIterator は基本的に List#listIterator() で取得します。

引数なしなら、先頭からの ListIterator を取得します。

引数ありなら、指定位置からの ListIteraot を取得します。

例えば、対象のリストが target だとします。

  • 引数なしと同じ位置は target.listIterator(0)
  • 先頭1つを飛ばす場合は target.listIterator(1)
  • 一番最後からの場合は target.listIterator(target.size())

要素を追加する場合

リストの要素を追加したい場合には add() を使います。
よくあるケースは、既にある要素を複数に分割する場合などでしょうか。
(単純に追加する場合もあると思いますが)

List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));

ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
    int num = iterator.next();
    if (num == 0) {
        // 0 は削除
        iterator.remove();

    } else if (num % 2 == 0) {
        // 2 で割り切れる場合には、分割
        iterator.remove();
        int sub = num / 2;
        iterator.add(sub); // ★
        iterator.add(sub); // ★
    }
}

System.out.println(list); // -> [3, 1, 2, 2, 1, 5, 9, 1, 1, 3, 3]

add() で追加した要素は previous() 方向に追加されます。
つまり、以下のようになります。

  • 次に next() を呼び出しても取得できない
  • 次に previous() を呼び出すと取得できる

先ほどは remove() した後に add() しましたが、単純に add() すると以下のようになります。

List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));

ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
    int num = iterator.next();
    if (num == 0) {
        // 0 は削除
        iterator.remove();

    } else if (num % 2 == 0) {
        // 2 で割り切れる場合には、分割
        iterator.remove();
        int sub = num / 2;
        iterator.add(sub);
        iterator.add(sub);

    } else if (num == 1) {
        // 1 の後には 999 を追加
        iterator.add(999); // ★
    }
}

System.out.println(list); // -> [3, 1, 999, 2, 2, 1, 999, 5, 9, 1, 1, 3, 3]

要素を置換したい場合

リストの要素を置換したい場合には set() を使います。

List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));

ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
    int num = iterator.next();
    if (num == 0) {
        // 0 は削除
        iterator.remove();

    } else if (num % 2 == 0) {
        // 2 で割り切れる場合には、分割
        iterator.remove();
        int sub = num / 2;
        iterator.add(sub);
        iterator.add(sub);

    } else if (num == 1) {
        // 1 の後には 999 を追加
        iterator.add(999);

    } else if (num == 5) {
        // 5 は 25 に置換
        iterator.set(25); // ★
    }
}

System.out.println(list); // -> [3, 1, 999, 2, 2, 1, 999, 25, 9, 1, 1, 3, 3]

set() は、前回 next() あるいは previous() が返したものを置換します。
そのため、以下のような状況では例外が発生します。

  • next() あるいは previous() を呼び出す前
  • add() あるいは remove() を呼び出した後

(ちなみに remove() も同様に前回 next() あるいは previous() が返したものを削除します)

逆方向にも進めたい場合

イテレータは順方向にしか進めませんが、 ListIterator は逆方向にも進めます。

よくあるケースは要素を複数に分割した際に、分割した要素についてもさらに処理をしたい場合などでしょうか。

List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));

ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
    int num = iterator.next();
    if (num == 0) {
        // 0 は削除
        iterator.remove();

    } else if (num % 2 == 0) {
        // 2 で割り切れる場合には、分割
        // さらに分割したものも処理する
        iterator.remove();
        int sub = num / 2;
        iterator.add(sub);
        iterator.add(sub);
        iterator.previous(); // ★
        iterator.previous(); // ★

    } else if (num == 1) {
        // 1 の後には 999 を追加
        iterator.add(999);

    } else if (num == 5) {
        // 5 は 25 に置換
        iterator.set(25);
    }
}

System.out.println(list); // -> [3, 1, 999, 1, 999, 1, 999, 1, 999, 1, 999, 1, 999, 25, 9, 1, 999, 1, 999, 3, 3]

単純に、リストの後ろから処理したいという場合もあるかもしれません。
その場合 hasNext() , next() の代わりに hasPrevious() , previous() を使うことで実現できます。

気をつけないといけないのは add()previous() 方向に追加するので、次の previous() で結果が返ってきてしまうことです。

java.util.Enumeration について

古いコレクションライブラリなので、基本的に使わない…といいたいのですが、古いライブラリでは直接 Enumeration を返すメソッドがあったりするので、たまに使わないといけないです。
(デフォルトメソッドで、 Iterator 化してくれればいいのにね)

Collections#list() メソッドで、別の List に詰めなおすことができるので、拡張for文で扱いたい場合などはこれを使うと便利だと思います。
データ量が多い場合にはちょっと無駄な気がして不安になりますが。

その他

空の Iterator など

あまり使わないと思いますが、Java7 から空の Iterator などを取得するメソッドも用意されています。

  • Collections#emptyIterator()
  • Collections#emptyListIterator()
  • Collections#emptyEnumeration()

直接 Iterator を返された場合

いくつかのライブラリでは、Iterable#iterator() 以外に、直接 Iterator を返すメソッドがあります。
メソッド名は iterator() なのに Iterable を実装していないとか、メソッド名が異なるなどのケースがあると思います。

そのような場合には(Java8以降なら)ラムダ式やメソッド参照で Iterable 化することが可能です。

LinkedList<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));

// 逆順に処理する
for (int num : (Iterable<Integer>) list::descendingIterator) {
    System.out.println(num);
}

残念ながら、直接ラムダ式やメソッド参照を書けないので、キャストが必要です。

Iterator を Stream にする

参考: IteratorをStreamにする

プリミティブ型のIterator

Java8から java.util.PrimitiveIterator というプリミティブ型用の Iterator インタフェースが追加されています。
実際には PrimitiveIterator.OfInt などのサブインターフェースを実装することになると思いますが。

通常の Iterator ではオートボクシングされてしまいますが、追加されているメソッドを使えばプリミティブ型のまま使用できます。
プリミティブ型の Iterator を作る場合には、このインタフェースを実装するとよいかもしれません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away