目的
- 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
を返すメソッドがあったりするので、たまに使わないといけないです。
Java9 で asIterator()
というデフォルトメソッドが用意されたので、 Iterator
化も簡単になりました。
enumeration.asIterator().forEachRemaining(i -> foo(i));
また Collections#list()
メソッドで、別の List
に詰めなおすことができるので、拡張for文で扱いたい場合などはこれを使うと便利だと思います。
データ量が多い場合にはちょっと無駄な気がして不安になりますが。
for (var i : Collections.list(enumeration)) {
foo(i);
}
逆に、既存の Collection
を Enumeration
にしたいケースでは Collections#enumeration()
が使えます。
@Override
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(map.keySet());
}
その他
空の 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
Java8から java.util.PrimitiveIterator
というプリミティブ型用の Iterator インタフェースが追加されています。
実際には PrimitiveIterator.OfInt
などのサブインターフェースを実装することになると思いますが。
通常の Iterator ではオートボクシングされてしまいますが、追加されているメソッドを使えばプリミティブ型のまま使用できます。
プリミティブ型の Iterator を作る場合には、このインタフェースを実装するとよいかもしれません。