Java の foreach を使い倒す

ループというのはプログラムでもっとも重要な要素の一つであり、どのような構文をもつかでその言語の一つの顔ができるものだ。Ruby の times なんてわりと好きだが、関数型の fold, map, iter なんかもいい。Python の for も iterator ベースで、良く設計できている。Java にも foreach が出現して大変便利なんだがちょっと貧弱だよね、例えば iteri (ループしながら何周目かカウントしたい)しようとしたとたんに、昔の for を使ったり、ループの外で一つ変数を宣言するのはダサくね、とおもっていたが実はわりと簡単に回避できるのだ。

public class WithIndex<T> implements Iterable<Pair<Integer, T>> {
  Iterable<T> container;

  public WithIndex(Iterable<T> container) {
    this.container = container;
  }

  public static <S> WithIndex<S> make(Iterable<S> container) {
    return new WithIndex<S>(container);
  }
  
  public Iterator<Pair<Integer, T>> iterator() {
    final Iterator<T> it = container.iterator();
    final int[] index = new int[1]; 
    
    return new Iterator<Pair<Integer, T>>() {
      public boolean hasNext() {
        return it.hasNext();
      }
      public Pair<Integer, T> next() {
        T next = it.next();
        return new Pair<Integer, T>(index[0]++, next);
      }
      public void remove() {
        it.remove();
        index[0]--;
      }
    };
  }
}

static の make 関数は、いわゆる make_pair 的イディオム。Pair というクラスが Java にないのがイケテない。自前で用意。こんな感じに使える。

    for (Pair e: WithIndex.make(v)) {
      System.out.println("" + e.getFirst() + ": " + e.getSecond());
    }

分かると思うが、getFirst() でインデックスが返ってくる。v は Vector あたり。あと、余談だけど final にしないと無名クラスの中で使えないせいで、その中で書き換えできないじゃんという人は、長さ 1 の配列にするといいよ。それがダサイなら Reference 型でもつくればいい。

こんな感じに応用すれば、コピーを作らずに zip(リストのペアをペアのリストに)ができたり、bigram に対して foreach したり、foreach でファイルアクセスできますよ。明日から楽しい iterable ライフを。