🌀

入門Iterator / Enumeration

に公開

環境

  • JDK 21

Iteratorとは?

概要

Iterator は、リストなどのコレクションから全要素を1つずつ取得するためのインタフェースです。

主なメソッドは以下の2つです。

  • hasNext()
    • 次の要素があればtrueを返します。
  • next()
    • 次の要素を返します。次の要素が無い場合はNoSuchElementExceptionをスローします。

ListSetIterable インタフェースを継承しています。そしてIterableインタフェースには、Iteratorを取得できるiterator()メソッドが定義されています。

これらを踏まえて、サンプルコードを見てみましょう。

ListIteratorMain.java
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
SetIteratorMain.java
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を取得します。

MapIteratorMain.java
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の内部クラスItrIterator実装として使われます。詳しく知りたい方は、各クラスのソースコードを読んでみてください。

Iteratorと拡張for文

先ほど紹介したIteratorのサンプルコードのような書き方は、ほとんどしないと思います。リストやセットの全要素を1つずつ取得するには、拡張for文がよく利用されます。

ListForMain.java
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または配列のみ指定できます。ListSetIterableを継承しているので、このように拡張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デザインパターンの一種です。デザインパターンについてもっと知りたい方は、以下の本を読んでみてください。

Enumerationとは?

概要

EnumerationIteratorと同様に、リストなどのコレクションから全要素を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