Javaのループ内でインデックス番号を取得する時の考察


## はじめに
Javaでループを書く場合は、拡張for文で書くことが一般的かと思います。
ですが、Rubyで言うところの`each_with_index`みたいにインデックス番号をつけてループ処理を行う方法はどのようにするべきか、考えてみました。  
サンプルとして作ったソースは[こちら](https://github.com/tsuka611/java_loop_sample)にあります。

## 普通のループを駆使してみる
標準の機能を利用して実現した場合を考えてみます。

### 通常のforループを利用するパターン
インデックスを利用して、ループを回す方法として真っ先に思いつくパターンです。
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/LoopSample.java#L14-L18)

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");
// 通常のforループを利用するパターン
for (int i = 0; i < list.size(); i++) {
	String s = list.get(i);
	Logic.doSomething(s, i);
}

```
この書き方の場合、`要素取得時にランダムアクセスが発生してしまうところ`が弱点になりそうです。`配列やArrayList`ならば問題ないですが、`LinkedList`などのリスト構造の場合は不利になってしまいます。内部実装に依存するロジックというのも違和感がありますし、そもそもJavaでは拡張for文がありますので、そちらを利用したいところです。

### 拡張for文と変数を利用するパターン
上述の弱点を補いつつ、ループカウンタとなる変数によってインデックス番号を取得するパターンです。
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/LoopSample.java#L22-L26)

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");

// 拡張for文で実現するパターン
int i = 0;
for (String s : list) {
	Logic.doSomething(s, i++);
}
```
拡張for文は`Iterableを実装したクラスおよび配列`で利用できます。拡張for文では、対象のイテレータを利用して順次アクセスを試みます。これなら上述のランダムアクセスによる弱点がなくなりますので、こちらの方がより推奨される形になるでしょう。  
ただ、個人的に気になるのが、カウンタ変数のスコープがループの外になってしまう点です。この場合、後続ロジックでも`変数i`が参照できてしまう点がどうにも気持ち悪いかなぁと感じます。細かいところが気になるのであれば、`{}`で囲んでスコープを閉じる方法も考えられますが、それだとネストが深くなってしまうのでなんとも言えません・・・

### forEachとラムダ式を利用してみるパターン
せっかくのJava8ということで`forEach`や`ラムダ式`を使ってみるパターンも考えてみます。
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/LoopSample.java#L30-L32)

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");
// forEachとラムダ式を利用するパターン
AtomicInteger i = new AtomicInteger();
list.forEach(s -> Logic.doSomething(s,i.getAndIncrement()));
```

ラムダ式の内部は`あるインターフェースを実装する匿名クラスを作成する糖衣構文`と見ることができます。したがって、ラムダ式内部から外側のスコープの変数にアクセスするためには`final（代入不可能）`である必要があります。したがってループカウンタとして`int`などのプリミティブ型を使うことができません（インクリメントする用途にfinalな変数は使えません・・・つまり詰んでます・・・）。
上記はソース的にはすっきり書けてはいますが、`AtomicInteger`の同期処理部分のオーバーヘッドや変数iのスコープの問題など、いろいろと不利になりそうなところが結構見受けられます。どうしてもラムダ式で書きたいこだわりがあるのなら、って感じでしょうか。

### ラムダ式を使わずにforEachを利用するパターン
`forEach`メソッドの引数は`Consumer`インターフェースの実装クラス（のインスタンス）です。上述のラムダ式は、匿名クラスをその場で定義してインスタンスを渡しているだけでしたので、自分で匿名クラスを記述しても問題ないわけです。したがって以下のような実装が可能です。
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/LoopSample.java#L36-L44)

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");
// 匿名クラスを使って無理やり変数を閉じ込めたパターン
list.forEach(new Consumer<String>() {
	private int index = 0;

	@Override
	public void accept(String s) {
		Logic.doSomething(s, index++);
	}
});

```
変数のスコープや同期の問題など、様々な問題はクリアできているように見えますが・・・なんとなくイケテナイ気がしてしまいます・・・

## 自分でクラスを作ってみる
自分でクラスやメソッドを作成することで、より便利な方法はないかを検討してみます。
まずはループで利用するデータと、インデックス番号を保持するクラスを作成し、こちらを利用したいと思います。（今後こちらを共通で利用します）
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/ValWithIndex.java)

```java
public class ValWithIndex<T> {
	private final T val;
	private final int index;

	public ValWithIndex(T val, int index) {
		this.val = val;
		this.index = index;
	}

	public T getVal() {
		return val;
	}

	public int getIndex() {
		return index;
	}
}

```
オブジェクトはジェネリクスで型を守りつつ、getterのみを保持する不変オブジェクトっぽくしてみました。（indexに引数チェックを入れても良さそうです）

### 拡張for文に適用させるパターン
拡張for文の中で上述のインスタンスを順次受け取ることができれば使い勝手が良さそうです。なので、以下のようなメソッドを作成します。（引数チェックなどは省略しています、ご了承ください・・・）
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/IterableSample.java)

```java
public static <T> Iterable<ValWithIndex<T>> withIndex(Iterable<T> itr) {

    return () -> new Iterator<ValWithIndex<T>>() {
        private int index = 0;
        private Iterator<T> it = itr.iterator();

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

        @Override
        public ValWithIndex<T> next() {
            return new ValWithIndex<>(it.next(), index++);
        }
    };
}
```
ちょっと複雑になってしまいました・・・
このメソッドはイテレートしたい対象を引数で受け取り、そちらをラップした`Iterable`のインスタンスを返します。`Iterable`は`iterator`という唯一のメソッドしか持っていないため、ラムダ式で匿名クラスを記述することが可能です。
`iterator`メソッドからは、`Iterator`を実装する匿名クラスのインスタンスを返しています。こちらはメソッドが複数あるのでラムダ式では書けません。内部の処理は基本的に引数でもらったイテレータに処理を移譲しています。（nextの終端判定などはもう少し考慮の必要がありそうです）

メソッドの利用方法は以下の通りです。

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");

// 拡張for文に対応させたパターン
for (ValWithIndex<String> v : withIndex(list)) {
    Logic.doSomething(v.getVal(), v.getIndex());
}

```

利用する側は結構すっきりです。

### Streamの途中に挟むパターン
上述の通り、処理対象となるオブジェクトとインデックス番号のペアを作成することができれば、ラムダ式の中でも利用できるかもしれません。`Stream#map`にてオブジェクトを変換するパターンをやってみます。
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/MappingSample.java)

```java
public static <T> Function<T, ValWithIndex<T>> withIndex() {
    return new Function<T, ValWithIndex<T>>() {
        private int index;

        @Override
        public ValWithIndex<T> apply(T t) {
            return new ValWithIndex<>(t, index++);
        }
    };
}
```

`Stream#map`メソッドに適用できる`Functionインターフェースの実装クラス`を作成しています。実際の利用シーンは以下の通りです。

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");

// Streamの間に挟んだパターン
list.stream().map(withIndex()).forEach(v -> Logic.doSomething(v.getVal(), v.getIndex()));

```

一見すると、良さそうなのですが、`Stream#parallelStream`を利用された場合の動作が全く保障できません。そもそも並列実行される前提におけるオブジェクトの順序（インデックス番号）とはどういったものなのでしょうか・・・おそらくこの辺の問題があるので、標準ライブラリには実装が行われていないのかなぁと思います。
`並列処理では使わない前提`を置くのであれば、この書き方でも良いかと思います。しかし`StreamAPIを前提とするメソッド`という側面を考えるとやや矛盾している気もするので、少しだけ気持ち悪いです。

## それでもせっかくなのでラムダ式を利用してみる
せっかくなのでラムダ式を・・・と言いたいところですが、`StreamAPI`を利用する場合、並列実行時に色々と問題があるようです。ですが、単にラムダ式ですっきり書きたければ`Iterable#forEach`を参考にするのが得策だと考えられます。

### forEachメソッドの実装について
Java8で`StreamAPI`と`ラムダ式`が追加され、ついでに`forEach`メソッドも追加されました。個人的にも勘違いしていたのですが、`Stream#forEach`と`Iterable#forEach`は実は全くの別物でした。以下は実際のソースの一部です。

```java
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

```
`Iterable`インターフェースのデフォルト実装として渡されています。（かなりワクワクしました）
ご覧の通り`Iterable#forEach`は順次実行での動作を行っています。したがって、こちらを参考にすれば色々な問題は解決するような気がします。

### forEachメソッドを参考に順次実行させるパターン
`StreamAPI`から切り離し、順次実行するメソッドとして定義した場合は以下のように書けそうです。（引数チェックなどは省略してます）
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/CallbackSample.java)

```java
public static <T> void eachWithIndex(Iterable<T> itr, ObjIntConsumer<T> action) {
    int index = 0;
    for (T t : itr) {
        action.accept(t, index++);
    }
}
```

インデックス番号と処理対象オブジェクトを受け取るために、`ObjIntConsumer`を引数で受け取ります。こうすることで、先ほどまでのように独自のクラスを使わなくても実現可能になりました。

利用方法は以下の通りです。

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");

// ラムダ式による順次実行のパターン
eachWithIndex(list, (v, i) -> Logic.doSomething(v, i));

```

かなりすっきりした気がします。

引数のシグニチャが若干気になる場合は、`Iterable`のサブインターフェースを利用してみるパターンも考えられます。（気持ち流れるような印象になりました）
[サブインターフェースのサンプル](https://github.com/tsuka611/java_loop_sample/blob/master/src/WithIndexIterable.java)

```java
public interface WithIndexIterable<T> extends Iterable<T> {

    default void eachWithIndex(ObjIntConsumer<T> action) {
        int index = 0;
        for (T t : this) {
            action.accept(t, index++);
        }
    }

    static <T> WithIndexIterable<T> iterate(Iterable<T> itr) {
        return () -> itr.iterator();
    }
}
```

[使い方サンプル](https://github.com/tsuka611/java_loop_sample/blob/master/src/AnotherCallbackSample.java)

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");

// ラムダ式による順次実行の別パターン
WithIndexIterable.iterate(list).eachWithIndex((v, i) -> Logic.doSomething(v, i));

```

## 結論
ちょっと長くなってしまいましたが、個人的な見解です。

- ライトに実現するなら`拡張for文+ループカウンタ`が良い。
- 順次処理のメソッドを用意する場合は`Iterable#forEachを参考にする`のが良い。

といった感じでしょうか。

## おまけ
### そもそもインデックス番号って必要なのだろうか？
元も子もないことを書いてありますが、インデックス番号を使って行いたい処理ってそこまで多くのパターンが無いのかなぁと思うわけです。
この辺りの用途が非常によくまとまっていると感じるのは、`JSTLのforEachにあるvarStatus`かなぁと思います。
`varStatus`で参照できるプロパティは以下の通りです。

|プロパティ|備考|
|----|----|
|current|現在のオブジェクト|
|index|現在のループのインデックス番号(0 origin)|
|count|現在のループのカウント数(1 origin)|
|first|現在がループの先頭かどうか|
|last|現在がループの最後かどうか|
|begin|begin属性に指定した値|
|end|end属性に指定した値|
|step|step属性に指定した値|

つまり、このあたりの情報があれば、利用する側としては使いやすいのではないかと思います。

### varStatusっぽいクラスを自作したパターン
簡易的に行うために、内部クラスを利用しています。
[サンプル本体](https://github.com/tsuka611/java_loop_sample/blob/master/src/LoopStatusSample.java)

```java
public static class Status<T> {
    private T current;
    private int index;
    private boolean isLast;

    private Status() {
    }

    private Status update(T current, int index, boolean isLast) {
        this.current = current;
        this.index = index;
        this.isLast = isLast;
        return this;
    }

    public T getCurrent() {
        return current;
    }

    public int getIndex() {
        return index;
    }

    public int getCount() {
        return index + 1;
    }

    public boolean isFirst() {
        return index == 0;
    }

    public boolean isLast() {
        return isLast;
    }
}

public static <T> void forEach(Iterable<T> itr, Consumer<Status<T>> action) {
    Status sts = new Status();
    int index = 0;
    Iterator<T> it = itr.iterator();
    while (it.hasNext()) {
        action.accept(sts.update(it.next(), index++, !it.hasNext()));
    }
}

```
ステータスのオブジェクトを使い回すパターンで実現しました。
今までの例では、オブジェクトを都度作成していましたが、ループ回数が多くなった場合にはパフォーマンスのオーバーヘッドが気になる場合もありますので、こちらの方がより良い形かと思われます。

実際の利用は以下の通りです。

```java
List<String> list = Arrays.asList("AAA", "BBB", "CCC");

// ステータスを利用したパターン
forEach(list, v -> {
            if (v.isFirst()) {
                Logic.doFirst(v.getCurrent());
            } else if (v.isLast()) {
                Logic.doLast(v.getCurrent());
            } else {
                Logic.doOther(v.getCurrent());
            }
        }
);

```

まぁ、だいたいこんな感じですかね。