Edited at

Lambda式を使ってIteratorを定義する

More than 3 years have passed since last update.


通常のIteratorの使い方

配列の要素を返すIteratorは通常、次のように定義します。

        int[] array = {3, 1, 4, 1};

Iterator<Integer> iterator = new Iterator<Integer>() {
int i = 0;

@Override
public boolean hasNext() {
return i < array.length;
}

@Override
public Integer next() {
return array[i++];
}
};

IteratorはhasNext()next()の二つのメソッドを定義する必要があるため、これを単純なLambda式に置き換えることはできません。


二つのLambda式からIteratorを作成する

そこで、二つのLambda式をコンストラクタで受け取るようなIteratorの実装クラスを作成します。

    public class LambdaIterator1<T> implements Iterator<T> {

final Supplier<Boolean> hasNext;
final Supplier<T> next;

public LambdaIterator1(Supplier<Boolean> hasNext, Supplier<T> next) {
this.hasNext = hasNext;
this.next = next;
}

@Override
public boolean hasNext() {
return hasNext.get();
}

@Override
public T next() {
return next.get();
}
}

これを使って、さっきと同じことをやってみます。

        int[] array = {3, 1, 4, 1};

int i = 0;
Iterator<Integer> iterator = new LambdaIterator1<>(
() -> i < array.length, () -> array[i++]);

すっきりしましたが、i++の部分がコンパイルエラーになってしまいます。Lambda式が参照する自由変数は変更されてはいけないためです。final int i = 0;としても結果は同じです。

以下のようにすればコンパイルはできますが、コンパイルエラーを回避するためだけにiを配列化しているため、分かりにくいコードになってしまいます。

        int[] array = {3, 1, 4, 1};

int i[] = {0};
Iterator<Integer> iterator = new LambdaIterator1<>(
() -> i[0] < array.length, () -> array[i[0]++]);


Lambda式が参照するコンテキストを追加する。

自由変数を使用せずLambda式が引数として受け取ることのできるコンテキストを追加してみます。

    public class LambdaIterator2<T, C> implements Iterator<T> {

final C context;
final Function<C, Boolean> hasNext;
final Function<C, T> next;

public LambdaIterator2(C context, Function<C, Boolean> hasNext, Function<C, T> next) {
this.context = context;
this.hasNext = hasNext;
this.next = next;
}

@Override
public boolean hasNext() {
return hasNext.apply(context);
}

@Override
public T next() {
return next.apply(context);
}
}

そうすると先の例は以下のようになります。

        int[] array = {3, 1, 4, 1};

Iterator<Integer> iterator = new LambdaIterator2<>(
new Object() { int i = 0; },
c -> c.i < array.length, c -> array[c.i++]);

変数iは自由変数ではなくなったので変更可能です。コンテキスト(第1引数)は無名の内部クラスですが、フィールドやメソッドを自由に定義できて、それらをLambda式の中で使うことができます。Objectのサブクラスである必要もありません。任意のオブジェクトを使用できます。

LambdaIterator2に以下のメソッドを追加しておけば、IterableStreamも簡単に作れるようになります。

        public Iterable<T> iterable() {

return () -> this;
}

public Stream<T> stream(boolean parallel) {
return StreamSupport.stream(iterable().spliterator(), parallel);
}

public Stream<T> stream() {
return stream(false);
}