Java Tips:Iterator、EnumerationなどループあれこれJAVA Developer

コレクションの全要素を列挙するお作法は,クラスごとに異なるため,覚えるのが面倒です。ここでは各種コレクションクラスの列挙を簡単にまとめました。

» 2004年07月13日 10時53分 公開
[JAVA Developer]

コレクションと列挙

 java.utilパッケージにはコレクションを表現するクラスが多数あります。配列では要素数が固定になりますから,不定数のオブジェクトを扱うときには便利に使えます。J2SE 5では,Genericsを導入してさらに使いやすく安全になります。

 さらに,java.utilパッケージ以外にもコレクションを表現するものがいくつかあります。代表的なものは,JDBCプログラミングでよく使うjava.sql.ResultSetクラスがあります。ResultSetクラスも,広義には不定数のオブジェクトを保持するコレクションクラスといえます。これら広義のコレクションクラスが今回の主役です。

 コレクションなどから要素を全部取得することを「列挙」と呼びます。コレクションの要素を列挙するには,特定の判定メソッドを用いながらループ処理を行う必要があります。疑似コードを示すと,次のようになります。

while (collection.hasMore())
{
    Object o = collection.getNext();
      :
    (コレクションから取り出したオブジェクトoに対する処理)
      :
}

 この例では,コレクションクラスのオブジェクトがcollectionで,そのクラスは判定メソッドとしてhasMoreメソッドを持ち,オブジェクトの取得メソッドとしてgetNextを持つものとしています。hasMoreメソッドがtrueを返せばまだ要素があることを意味し,falseを返せばもう取得できる要素がないことを意味します。

 このようなコードを用いて,ループで全要素を取得しますが,このときクラスによって判定メソッドが異なります。また要素を取得するメソッド(この例ではgetNextメソッド)も異なります。プログラムを書いていると,「あれ? このクラスはどうやってループを回したっけ?」と思うことがたびたびあります。

 そこで,サーバーサイドでもよく使うコレクションクラスに関して,ここで簡単にまとめてみましょう。

java.util.Iteratorによるループ

 まず最初は,J2SE1.4以降のコレクションで最もよく使うIteratorによるループです。java.utilパッケージのほとんどのコレクションクラスは,Iteratorインタフェースを実装しているため,この手法が使えます。また,バリエーションとしてListIteratorというインタフェースもあります。

java.util.ArrayList list = new java.util.ArrayList();
java.util.Iterator ite = list.iterator();
while (ite.hasNext())
{
    Object ipBean = (Object)ite.next();
}

 Iteratorでは,コレクションからIteratorオブジェクトを取得したら,Iteratorは初期状態にあり,先頭のオブジェクトを指しています。

 オブジェクトが取得できるかどうかは,hasNextメソッドで判定します。このメソッドがtrueを返せば,nextメソッドでコレクションが保持しているオブジェクトを取得できます。取得することにより,コレクション内部のポインタが1つ進み,次のオブジェクトを指すようになります。コレクションが空の場合は,初回のhasNext呼び出しでfalseを返します。

 hasNextメソッドがfalseを返せば,もう要素はありません。一般には,そのIteratorオブジェクトは用済みになります。

 なお,ListIteratorを使うと,先頭からだけでなく,末尾から順に取得したり,ループ中にポインタを行ったりきたりさせることも可能になります。

ResultSetによるループ

 データベースプログラミングでJDBCを用いると,必ず登場するのがResultSetインタフェースです。これはデータベースの行をコレクションとして持ちます。

 ループのコード例は次のようになります。ここではStatement#executeQueryメソッドでResultSetを取得しています。

java.sql.Statement stt = null;
java.sql.ResultSet rs = stt.executeQuery("select * from table");
while (rs.next())
{
    String text = rs.getString("title");
}
rs.close();

 要素の判定はResultSet#nextメソッドで行います。データベースの場合,行のオブジェクトを取得するのではなく,さらにその中から列を指定してデータを取得します。

 ResultSetは,利用がすめばcloseメソッドでリソースを開放することを忘れないようにします。

列の列挙はかなり違う

 ResultSetは,行の集合を表現すると同時に列の集合も表現しています。行方向のループはnextメソッドで取得できますが,列方向の列挙は簡単ではありません。ここではMySQLの例を示しますが,RDBMSによっては動作が異なることがあります。

Class.forName("org.gjt.mm.mysql.Driver");
java.sql.Connection dbConn = java.sql.DriverManager.getConnection("JDBC URL");
java.sql.Statement sttSql = dbConn.createStatement();
java.sql.ResultSet rs = sttSql.executeQuery("select * from table");
java.sql.ResultSetMetaData rsmd = rs.getMetaData();
int iCols = rsmd.getColumnCount();
int column = 1;
while (column <= iCols)
{
    String columnName = rsmd.getColumnName(column);
    ++column;
}
rs.close();

 この例では,「table」という表のカラム名を列挙しています。このコードでわかるように,列挙の判定メソッドでループを回すのではなく,java.sql.ResultSetMetaDataに対してインデクス番号でアクセスします。

Enumerationによるループ

 java.util.Enumerationは,Javaのコアパッケージの中ではIteratorより古くからある列挙用のインタフェースです。現在,このインタフェースを使うメリットはほとんどありませんが,ここでは記事の趣旨上避けて通れません。

java.util.Enumeration enu = new java.util.Vector().elements();
while (enu.hasMoreElements()) 
{
    String text = (String)enu.nextElement();
}

 VectorなどのクラスがEnumerationオブジェクトを持ちます。Enumerationの取得にelementsメソッドを用いるなど,やや一貫性のなさを感じますが,ループのスタイルはIteratorと大きく変わりません。

StringTokenizerにおけるループ

 Enumerationの応用として,Enumerationを実装するjava.util.StringTokenizerの例も取り上げましょう。StringTokenizerクラスは,文字列を特定の区切り文字で区切って取得するためのクラスです。たとえば「a, b, c」のようなカンマ区切りの文字列を,カンマごとに区切って取得するような用途に用います。このとき,区切られる文字列の数は不定ですから,列挙にEnumerationを用います。

java.util.StringTokenizer st = new java.util.StringTokenizer("a,b,c", ",");
while (st.hasMoreTokens())  // hasMoreElements
{
    String token = st.nextToken();  // nextElement()
}

 ただし,StringTokenizerクラスは,hasMoreElementsと同等の機能をhasMoreTokensというメソッドでも提供し,nextElementと同等の機能をnextTokenというメソッドでも提供しています。どちらを使っても振る舞いは同じですが,このクラスの目的からいって,返すのは必ずStringオブジェクトですから,nextTokenメソッドでの返り値はString型になっています。

 オブジェクトの取得にはnextToken(String delim)というメソッドもあります。これは取得するたびに区切り文字を変化させたいときに用います。

Matcherにおけるループ

 StringTokenizerと同様に個別用途のクラスですが,正規表現の実装クラスであるjava.util.regex.Matcherも列挙機能を持ちます。これは,文字列から正規表現でパターンマッチングを行い,不定数のマッチ情報を列挙するためのものです。

 次の例では,「CharSeq」という文字列から,「RE」というパターンを検索します。この組み合わせでは見つからないはずです。

java.util.regex.Pattern pat = java.util.regex.Pattern.compile("RE");
java.util.regex.Matcher mch = pat.matcher("CharSeq");
while (mch.find())
{
    String res = mch.group();
}

 ループの判定メソッドはMatcher#findになります。次の検索結果があればtrueを返します。この状態で,Matcherオブジェクトは該当マッチに関する情報を保持しています。

HashMapにおけるループ

 最後に,応用例としてjava.util.HashMapの要素列挙を見てみましょう。HashMapには,キーと値のペアが保持されていますが,直接はIteratorを取得できません。

 次の例をご覧ください。この例は2つのHashMap,hmSrcからhmDestに全要素をコピーする例です。

java.util.HashMap hmSrc = new java.util.HashMap();
java.util.HashMap hmDest = new java.util.HashMap();
java.util.Set stKey = hmSrc.entrySet();
java.util.Iterator ite = stKey.iterator();
while (ite.hasNext())
{
    Object o = ite.next();
    java.util.Map.Entry ent = (java.util.Map.Entry)o;
    String key = (String)ent.getKey();
    String val = (String)ent.getValue();
    hmDest.put(new String(key), new String(val));
}

 列挙にはentrySetメソッドでjava.util.Setを取得して,そこからIteratorを取得します。

 このように,同じjava.utilパッケージに含まれるコレクションでも,用途によって,作られた世代によって,少しずつ扱いが異なることに注意が必要です。

 「JAVA Developer」より毎週役立つJava Tipsを配信中。ほかにも参考になるTipsは、JAVA Developerサイトのバックナンバーから探すことが可能です。

Copyright(C) 2010 SOFTBANK Creative Inc. All Right Reserved.

注目のテーマ