コードを書いているとたまにふたつのIteratorをいっしょに回すコードを書くはめになる。
/** ふたつのItaratorを並べて回すサンプル */ static boolean compare1(List<String> list1, List<Integer> list2) { if (list1.size() != list2.size()) { throw new IllegalArgumentException("個数の不一致"); } Iterator<String> ite1 = list1.iterator(); Iterator<Integer> ite2 = list2.iterator(); // 敢えてショートサーキットしない&演算を用いる while (ite1.hasNext() & ite2.hasNext()) { String v1 = ite1.next(); int v2 = ite2.next(); if (Integer.parseInt(v1) != v2) { return false; } } return true; }
単一のListを処理する場合はfor-each構文でループさせるところだけど(これはJava7の話だと思っていただきたい)、ふたつの要素数の等しい前提のListをループさせる、となると面倒臭いことになる。上記コードはIteratorで記述したが、これなら古典的for文でループカウンタiを用意して以下のように書いたほうがマシなんじゃないかと思える。
/** ループカウンタ方式 */ static boolean compare2(List<String> list1, List<Integer> list2) { if (list1.size() != list2.size()) { throw new IllegalArgumentException("個数の不一致"); } for (int i=0; i<list1.size(); i++) { String v1 = list1.get(i); int v2 = list2.get(i); if (Integer.parseInt(v1) != v2) { return false; } } return true; }
なんかしっくりこないなー。と思っていたのだけど、ふたつのIteratorをまとめて回すIteratorを作ればいいことに気付いた。
まず、Javaにはタプルがないので適当にクラスを作る
public class Tuple<T1, T2> { public T1 t1; public T2 t2; public Tuple() {} public Tuple(T1 t1, T2 t2) { this.t1 = t1; this.t2 = t2; } }
そして、ふたつのIteratorを受け取ってTupleを返すようなIteratorの実装を書く。
import java.util.Iterator; /** * ふたつのIteratorを並べてループさせるためのItaratorのサンプル * * @author nagise * * @param <T1> Itarator1 で扱う型 * @param <T2> Itarator2 で扱う型 */ public class TupleIterator<T1, T2> implements Iterator<Tuple<T1, T2>>, Iterable<Tuple<T1, T2>> { Iterator<? extends T1> ite1; Iterator<? extends T2> ite2; /** * 2つのItaratorからTupleIteratorを作る */ public TupleIterator(Iterator<? extends T1> ite1, Iterator<? extends T2> ite2) { this.ite1 = ite1; this.ite2 = ite2; } /** * 2つのIterableからTupleIteratorを作る */ public TupleIterator(Iterable<? extends T1> ite1, Iterable<? extends T2> ite2) { this.ite1 = ite1.iterator(); this.ite2 = ite2.iterator(); } /** * @throws IllegalStateException ふたつのItaratorの要素数が不一致の場合 */ @Override public boolean hasNext() { boolean n1 = ite1.hasNext(); boolean n2 = ite2.hasNext(); if (n1 ^ n2) { throw new IllegalStateException("個数の不一致"); } return n1; } @Override public Tuple<T1, T2> next() { return new Tuple<T1, T2>(ite1.next(), ite2.next()); } @Override public void remove() { ite1.remove(); ite2.remove(); } @Override public Iterator<Tuple<T1, T2>> iterator() { return this; } }
といった記述になる。自作ライブラリが噛んできて面倒臭いところだが、設計的には綺麗になるかなあ。
細かい実装のポイント
java.lang.Iterable (いてらぶる)と java.util.Iterator (いてれーたー)が混在している。見た目が似ているので気をつけてみないと混同するかもしれない。
java.lang.Iterable はJava5からのinterfaceで、同じくJava5から追加になったfor-each構文のためのinterfaceである。このIterableを実装するクラスはfor-eachに投入してループさせることができる。
このinterfaceのメソッドは単一で
型 T の要素セットのイテレータを返します。
戻り値:
Iterable (Java Platform SE 7)
イテレータ。
このiterator()メソッドで返されたjava.util.Iteratorを用いてループするという寸法だ。
ここで面倒臭い話なのだが、java.util.Iterator(いてれーたー)はIterable(いてらぶる)ではない。そのため、Iterator(いてれーたー)オブジェクトを持ち回っている時、そのままではfor-eachのループに使えない。
そのため、Iterator(いてれーたー)の実装クラスはしばしばIterable(いてらぶる)もあわせてimplementsし、以下のようにreturn this;とする実装が見られる。
@Override public Iterator<Tuple<T1, T2>> iterator() { return this; }
個人的にはキモいんだが、利便性のためには妥協するところなのだろう。そもそもfor-eachがIteratorそのものを回せればこんなことにはならないんだけど。
追記
https://gist.github.com/yuba/9612538
iterator()でreturn this;とする実装だと複数回iterator()を呼ぶと破綻するとの指摘。確かに。初稿を書いている時に return thisがキモい理由がぱっと説明できなかったのだけどスッキリした。
おまけ
Javaでは^記号はXOR演算を表す。普段if文で使う機会は少ないだろうし、無理して使う必要もない :-P