みんな大好きStreamAPIですが、
拡張for文で書くようなループはキレイにかけますが、
ループ内部でインデックスを参照したいようなループはいまいちどうしたらいいのかわかりません。
(ぐぐっても独自クラス作っちゃうような方法がでてきたり)
ということでちょっと頭をひねってみました。
「ループ内部でインデックスを参照したいようなループ」としては以下のようなケースを想定します。
- 2つ以上のリストをまとめて回したい
- 1つのリスト中の隣接する要素(
i
に対してi-1
とかi+1
とか)も参照したい
(i
が参照できて、かつ好きな処理を書けるので、
「リストの最初/最後のときに処理を変えたい」とかのケースでも使えますが、
あんまりキレイじゃないかな、と思います。)
以下がコード例です。
public static void main(String[] args) {
// 乱数で作った適当なリスト
List<Integer> listA = new ArrayList<>(Arrays.asList(12, 23, 66, 45, 10, 48, 21, 25, 68, 65));
List<Integer> listB = new ArrayList<>(Arrays.asList(98, 96, 4, 33, 30, 38, 39, 25, 32, 64));
// ケース1:2つのリストをまとめて扱いたい場合
List<Boolean> isWinnerA = IntStream.range(0, Math.min(listA.size(), listB.size()))
.mapToObj(i -> listA.get(i) > listB.get(i))
.collect(Collectors.toList());
System.out.println("isWinnerAList: " + isWinnerAList);
// -> isWinnerA: [false, false, true, true, false, true, false, false, true, true]
// ケース2:リストの中の前後をまとめて扱いたい場合
List<Double> movingAverage = IntStream.range(0, listA.size() - 1)
.mapToObj(i -> (listA.get(i) + listA.get(i + 1)) / 2d)
.collect(Collectors.toList());
System.out.println("movingAverage: " + movingAverage);
// -> movingAverage: [17.5, 44.5, 55.5, 27.5, 29.0, 34.5, 23.0, 46.5, 66.5]
}
構成としては、
-
for(int i; i < list.size(); i++)
の部分-
IntStream
のファクトリメソッドで作ります - 例のような単純な増加なら
range(startInclusive, endExclusive)
でいけます - インクリメントの大きさを変えたいなら
iterate(seed, f).limit(maxSize)
とか(参照する方でi*2
してもいいですが) -
generate(s)
を使えば大抵のことはできるはず
-
- ループ内の処理
mapToObj(/* 変換処理 */).collect(/* 集約処理 */);
- 集約しなくて良いなら
forEach()
で済みますが。
pro:標準で用意されてるものだけで書ける
con:Stream慣れてる人じゃないと困惑するかも
普通にfor文で書いた方がいいって言われますかね・・・?
まぁ、例に使ってる程度のものなら普通にfor文書いてもいいかもですが、filter()
とか、集約処理とか、Streamの機能が活きるケースだと使えるかな?と思います。