入門Iterator / Enumeration
環境
- JDK 21
Iteratorとは?
概要
Iterator
は、リストなどのコレクションから全要素を1つずつ取得するためのインタフェースです。
主なメソッドは以下の2つです。
-
hasNext()
- 次の要素があれば
true
を返します。
- 次の要素があれば
-
next()
- 次の要素を返します。次の要素が無い場合は
NoSuchElementException
をスローします。
- 次の要素を返します。次の要素が無い場合は
List
やSet
は Iterable
インタフェースを継承しています。そしてIterable
インタフェースには、Iterator
を取得できるiterator()
メソッドが定義されています。
これらを踏まえて、サンプルコードを見てみましょう。
package com.example;
import java.util.Iterator;
import java.util.List;
public class ListIteratorMain {
public static void main(String[] args) {
// 適当なリストを定義
List<String> list = List.of("Alice", "Bob", "Charlie");
// リストのIteratorを取得
Iterator<String> iterator = list.iterator();
// 要素がある限り繰り返す
while (iterator.hasNext()) {
// 次の要素を取得
String name = iterator.next();
// 取得した要素を表示
System.out.println(name);
}
}
}
Alice
Bob
Charlie
package com.example;
import java.util.Iterator;
import java.util.Set;
public class SetIteratorMain {
public static void main(String[] args) {
// 適当なセットを定義
Set<String> set = Set.of("りんご", "バナナ", "すいか");
// セットのIteratorを取得
Iterator<String> iterator = set.iterator();
// 要素がある限り繰り返す
while (iterator.hasNext()) {
// 次の要素を取得(セットなので、定義時の順番で取得できるとは限らない)
String fruit = iterator.next();
// 取得した要素を表示
System.out.println(fruit);
}
}
}
バナナ
すいか
りんご
マップの場合は、キーと値のペアを保持するエントリーのセットを取得して、そのエントリーセットに対するIterator
を取得します。
package com.example;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapIteratorMain {
public static void main(String[] args) {
// 適当なマップを定義
Map<Integer, String> map = Map.of(
101, "Alice",
102, "Bob",
103, "Charlie"
);
// マップのエントリーセットを取得
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
// エントリーセットのIteratorを取得
Iterator<Map.Entry<Integer, String>> iterator = entrySet.iterator();
// 要素がある限り繰り返す
while (iterator.hasNext()) {
// 次の要素を取得(マップなので、定義時の順番で取得できるとは限らない)
Map.Entry<Integer, String> entry = iterator.next();
// 取得した要素のキーと値を表示
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
102 : Bob
103 : Charlie
101 : Alice
Iterator実装クラスはどこにある?
Iterator
実装クラスは、各コレクションクラスの内部クラスとして定義されていることがほとんどです。
例えばArrayList
クラスの場合、スーパークラスのAbstractList
の内部クラスItr
がIterator
実装として使われます。詳しく知りたい方は、各クラスのソースコードを読んでみてください。
Iteratorと拡張for文
先ほど紹介したIterator
のサンプルコードのような書き方は、ほとんどしないと思います。リストやセットの全要素を1つずつ取得するには、拡張for文がよく利用されます。
package com.example;
import java.util.List;
public class ListForMain {
public static void main(String[] args) {
// 適当なリストを定義
List<String> list = List.of("Alice", "Bob", "Charlie");
// 拡張for文で、リストから1つずつ要素を取り出して、要素がある限り繰り返す
for (String name : list) {
// 取得した要素を表示
System.out.println(name);
}
}
}
実は、拡張for文の :
の後ろにはIterable
または配列のみ指定できます。List
やSet
はIterable
を継承しているので、このように拡張for文を利用できます。
Java言語仕様の14.14.2. The enhanced for statementに"The type of the Expression must be an array type (§10.1) or a subtype of the raw type Iterable, or a compile-time error occurs."と書かれています。
Iteratorとデザインパターン
Iterator
は、いわゆるGoFデザインパターンの一種です。デザインパターンについてもっと知りたい方は、以下の本を読んでみてください。
-
オブジェクト指向における再利用のためのデザインパターン(改訂版)
- いわゆるGoF本。サンプルコードがC++で書かれている。
-
増補改訂版Java言語で学ぶデザインパターン入門
- 結城浩さんによる本。サンプルコードがJavaで書かれているので、読むならこちらをおすすめします。
Enumerationとは?
概要
Enumeration
もIterator
と同様に、リストなどのコレクションから全要素を1つずつ取得するためのインタフェースです。
メソッドも似ています。
-
hasMoreElements()
- 次の要素があれば
true
を返します。
- 次の要素があれば
-
nextElement()
- 次の要素を返します。次の要素が無い場合は
NoSuchElementException
をスローします。
- 次の要素を返します。次の要素が無い場合は
サーブレットのHttpServletRequest
インタフェースには、すべてのリクエストヘッダー名をEnumeration
形式で返すgetHeaderNames()
メソッドが定義されています。これを利用してすべてのリクエストヘッダーを表示してみます。
HttpServletRequest request = ...;
// 全リクエストヘッダーの名前を保持するEnumerationを取得
Enumeration<String> headerNames = request.getHeaderNames();
// ヘッダー名がある限り繰り返す
while (headerNames.hasMoreElements()) {
// 次のヘッダー名を取得
String headerName = headerNames.nextElement();
// 取得したヘッダー名に対応する値を取得
String headerValue = request.getHeader(headerName);
// 取得したヘッダー名と値を表示
System.out.println(headerName + ": " + headerValue);
}
EnumerationからIteratorへの変換
実はEnumeration
には、Iterator
に変換するasIterator()
メソッドが定義されています。
HttpServletRequest request = ...;
// 全リクエストヘッダーの名前を保持するEnumerationを取得
Enumeration<String> headerNames = request.getHeaderNames();
// EnumerationをIteratorに変換
Iterator<String> iterator = headerNames.asIterator();
// ヘッダー名がある限り繰り返す
while (iterator.hasNext()) {
// 次のヘッダー名を取得
String headerName = iterator.next();
// 取得したヘッダー名に対応する値を取得
String headerValue = request.getHeader(headerName);
// 取得したヘッダー名と値を表示
System.out.println(headerName + ": " + headerValue);
}
なぜ似たインタフェースが2つあるのか
Javadocを確認したところ、Enumeration
はJava 1.0から、Iterator
はJava 1.2から存在します。つまりEnumeration
の方が先に存在していました。
ここからは僕の予想なのですが、GoFデザインパターンで有名になったIterator
を、後から取り入れたのではないでしょうか。しかし既に存在していたEnumeration
を削除する訳にもいかないため、似たインタフェースが2つ存在することになってしまったのだと思います。たぶん。
Discussion