執筆のきっかけ
Javaのシステムを業務で触り始めてそれなりに慣れてきた頃合いで、
同じくJavaであるもののフレームワークが異なる別システムの業務を担当することになりました。
そのソースコードではよくStream APIが使用されていましたが、
ぱっと見てどのような処理が行われているのか瞬時に判断できなかった記憶があります![]()
(急ぎの修正により致し方なくfor文で記載して叱られたこともありました)
Stream APIの基本に合わせて、
よく使用していた処理をfor文での比較として併記して整理してみます![]()
Stream API について
Stream API は、Java8から導入された、
データの集合(Collectionなど)を、効率よく簡潔に処理するための仕組みです![]()
Stream API を使った処理は、以下の3ステップで構成されます。
- 生成 : List や Array などのデータソースから Stream を作成
- 中間操作 : フィルタリングや変換を実施
- 終端操作 : 結果のまとめ、表示
Stream API のメリット
従来の for文や if文によるループ処理と比較して、以下のようなメリットがあります![]()
- コードが簡潔になる: 「何をしたいか」という宣言的な記述ができるため、行数が大幅に減る
- 可読性が高い: 処理が順番に流れるように記述されるため、意図が伝わりやすい
- 副作用の抑制: 元のデータを変更せずに新しいコレクションを作成するため、安全なコードになる
Stream API でよく使用した処理 5選
① 条件で絞り込み(filter)
Streamあり
List<User> activeUsers = users.stream()
.filter(user -> user.isActive())
.collect(Collectors.toList());
Streamなし
List<User> activeUsers = new ArrayList<>();
for (User user : users) {
if (user.isActive()) {
activeUsers.add(user);
}
}
ポイント
- 「特定の条件で絞る」という処理の意図がそのまま書ける
- 体感としては最も使用頻度が高い処理
実務での使用例
DBから取得したデータや、別の共通処理で取得したデータの中に、
「退会済みユーザー」や「無効なアカウント」が混ざっているケースがありました。
不要なデータをfilterで事前に除外することで、後続の処理を簡潔にすることができました。
② 要素の変換(map)
Streamあり
List<String> names = users.stream()
.map(user -> user.getName())
.collect(Collectors.toList());
Streamなし
List<String> names = new ArrayList<>();
for (User user : users) {
names.add(user.getName());
}
ポイント
- 「別の形に変換する」という処理をシンプルに書ける
実務での使用例
DBから取得したエンティティをそのまま返せなかったため、
フロントに渡すレスポンス用DTOへ変換するために使用しました。
③ 合計・集計(mapToInt + sum)
Streamあり
int totalAge = users.stream()
.mapToInt(user -> user.getAge())
.sum();
Streamなし
int totalAge = 0;
for (User user : users) {
totalAge += user.getAge();
}
ポイント
- 数値の計算に関する処理を簡潔に書ける
- 平均(average)なども同様に書ける
実務での使用例
「ユーザーの平均年齢」や「合計金額」等を算出する処理で使用しました。
"sum"や"average"といった単語の意味からも、どのような処理を行っているのかわかりやすいです。
④ 条件に一致するものがあるか(anyMatch)
Streamあり
boolean hasAdmin = users.stream()
.anyMatch(user -> user.isAdmin());
Streamなし
boolean hasAdmin = false;
for (User user : users) {
if (user.isAdmin()) {
hasAdmin = true;
break;
}
}
ポイント
- 「特定の条件に合致するデータが1つでもあればOK」という条件がわかりやすい
実務での使用例
ユーザーの権限チェックで、「特定グループのユーザーの内、管理者ユーザーが含まれているか」を確認するために使用しました。
何かの条件に引っかかるデータを抽出という場面はそれなりに多かった印象です。
逆に、条件に引っかかるデータがないことを判定するには "noneMatch" を使用します。
⑤ 並び替え(sorted)
Streamあり
List<User> sortedUsers = users.stream()
.sorted(Comparator.comparing(User::getAge))
.collect(Collectors.toList());
Streamなし
List<User> sorted = new ArrayList<>(users);
Collections.sort(sorted, new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
return Integer.compare(u1.getAge(), u2.getAge());
}
});
ポイント
- 「何を条件にソートするか」を読み取りやすく、streamの処理の流れで自然に書ける
- filterやmapと組み合わせやすい
実務での使用例
ユーザー一覧画面で「年齢順」や「登録日順」などの並び替えを行う際に使用しました。
まとめ
実務でよく使用したStream APIを、for文での書き方と比較してみました。
Stream APIに慣れていない頃は無理に使おうとすると逆に読みにくくなることもありましたが、
「まずはシンプルなループ処理から置き換えてみる」ことで、少しずつ書き方に慣れることができました。
まだStream APIを使用したことがない方へ、少しでも参考になる部分があると幸いです![]()