到達点は、抽出、変換、集計を分けて書くことです。前提は第07・08回です。
「どう回すか」から「何を求めるか」へ
Streamはコレクションそのものではなく、要素を処理する流れです。filterで対象を絞り、mapで形を変え、sumやtoListなどの終端操作で結果を得ます。終端操作が呼ばれるまで、中間操作は原則として実行されません。
import java.util.List;
public class Main {
record Session(String language, int minutes) {}
public static void main(String[] args) {
List<Session> sessions = List.of(
new Session("Java", 25),
new Session("Rust", 30),
new Session("Java", 15)
);
int total = sessions.stream()
.filter(s -> s.language().equals("Java"))
.mapToInt(Session::minutes)
.sum();
List<String> names = sessions.stream()
.map(Session::language)
.distinct()
.sorted()
.toList();
System.out.println(total);
System.out.println(names);
}
}
Main.javaでの出力は40とJava・Rustのリストです。ラムダの s -> ... は引数から結果を作る小さな関数です。Session::minutes はminutesメソッドを呼ぶ処理を表すメソッド参照です。
流れの外側を変更しない
Stream内から共有変数へ加算するより、sumやcollectで結果を返すようにします。外部への書き込みを混ぜると、順序や実行回数に依存したバグが生まれます。peekを業務上必須の更新に使うのも避けます。
一つのStreamを終端操作後に再利用することはできません。別の計算には元のコレクションから新しくstreamを作ります。また、無理に長いチェーンへ詰め込む必要はありません。途中で複雑な分岐やエラー処理があるなら、普通のforの方が読みやすいこともあります。
parallelStreamを付ければ必ず速くなるわけではありません。要素の分割、結果の結合、共有スレッドの競合に費用がかかります。まず逐次処理で正しさを確認し、実測して選びます。
練習と解答
練習:20分以上の学習だけを対象に、合計時間を求めてください。対象が0件ならどうなりますか。
解答:sessions.stream().filter(s -> s.minutes() >= 20).mapToInt(Session::minutes).sum() で55です。空のIntStreamのsumは0です。平均を求めるaverageはOptionalDoubleを返すので、空の場合の意味を別に考えます。
公式資料
Stream APIを参照できます。
Java全20回の目次 | 前の回 | 次の回