こんにちは、かつコーチです。
前回はStream APIの全体像を解説しましたが、今回は実務で最もよく使う中間操作(Streamを加工して次の処理に渡す操作)であるfilter・map・sortedを掘り下げます。
中間操作とは?
中間操作の役割
中間操作はStreamを受け取り、加工した新しいStreamを返すメソッドです。
「中間」という名前の通り、それ単体では処理が実行されず、collectやforEachなどの終端操作が呼ばれて初めて実行されます(遅延評価)。
複数の中間操作をつなげてパイプライン(処理の流れ)を作れる点がStream APIの特徴です。
なぜ中間操作を分けて理解する必要があるのか
filterは「絞り込み」、mapは「変換」、sortedは「並び替え」と役割が明確に分かれています。
それぞれを単独で理解しておくと、組み合わせたときに「今どの処理が何をしているか」を追いやすくなります。
基本の書き方
filter:条件に合う要素だけを残す
import java.util.List;
record Product(String name, int price) {}
List<Product> products = List.of(
new Product("キーボード", 8000),
new Product("マウス", 3000),
new Product("モニター", 25000)
);
products.stream()
.filter(p -> p.price() >= 5000)
.forEach(p -> System.out.println(p.name()));
// キーボード
// モニター
filterはPredicate<T>(trueかfalseを返す判定処理)を引数に取り、条件を満たす要素だけを残します。
複数条件を組み合わせたい場合は、前回解説したPredicateのand・orが活用できます。
map:要素を別の値に変換する
List<String> names = products.stream()
.map(Product::name)
.toList();
System.out.println(names); // [キーボード, マウス, モニター]
mapはFunction<T, R>を引数に取り、要素を別の型・別の値に変換します。
「商品オブジェクトのListから商品名だけのListを作る」のような詰め替え処理でよく使います。
sorted:並び替える
products.stream()
.sorted((a, b) -> a.price() - b.price())
.forEach(p -> System.out.println(p.name() + ": " + p.price()));
// マウス: 3000
// キーボード: 8000
// モニター: 25000
sortedは引数なしなら自然順序(Comparableの実装に従った順序)、Comparatorを渡せば任意の基準で並び替えられます。Comparator.comparingInt(Product::price)のように書くと、より意図の伝わるコードになります。
よくあるつまずきポイント・エラー対処
filterとmapの順序を逆にして無駄な処理をしてしまった
実際に私が書いてしまったコードですが、mapを先に呼んで全件変換してからfilterで絞り込み、無駄な計算をしていたことがあります。
// ❌Before:全件を変換してから絞り込む(無駄が多い)
List<String> result = products.stream()
.map(p -> p.name() + "(" + p.price() + "円)") // 全件を文字列化
.filter(s -> s.contains("キーボード"))
.toList();
3件程度なら誤差ですが、件数が増えるほど「使わない変換」に時間がかかる無駄なコードになります。
// ✅After:先にfilterで絞り込んでからmapで変換する
List<String> result = products.stream()
.filter(p -> p.name().contains("キーボード"))
.map(p -> p.name() + "(" + p.price() + "円)")
.toList();
System.out.println(result); // [キーボード(8000円)]
「絞り込んでから変換する」順序にすると、変換処理の対象件数が減り、無駄な計算を避けられます。
中間操作は書いた順に実行されるため、処理コストが高い操作ほど後ろに置くのが基本のコツです。
応用・一歩先の使い方
複数の中間操作を組み合わせる
List<String> result = products.stream()
.filter(p -> p.price() < 20000)
.sorted(java.util.Comparator.comparingInt(Product::price))
.map(Product::name)
.toList();
System.out.println(result); // [マウス, キーボード]
filter→sorted→mapのように複数の中間操作をつなげることで、「絞り込んで、並び替えて、名前だけ取り出す」という一連の処理を1つの文で表現できます。
distinct・limit・skipも押さえておく
List<Integer> numbers = List.of(1, 2, 2, 3, 4, 4, 5);
List<Integer> result = numbers.stream()
.distinct() // 重複を除く
.skip(1) // 先頭1件をスキップ
.limit(2) // 先頭から2件だけ取得
.toList();
System.out.println(result); // [2, 3]
distinctは重複除去、skip・limitはページネーションのような「先頭からN件飛ばしてM件取る」処理に便利です。
まとめ
この記事のポイント
- 中間操作はStreamを加工して次に渡す操作で、それ単体では実行されない(遅延評価)
filterは絞り込み、mapは変換、sortedは並び替えとそれぞれ役割が異なる- 処理コストを抑えるには、絞り込み(
filter)を先に、変換(map)を後にするのが基本 distinct・limit・skipも組み合わせるとさらに柔軟な処理が書ける
次に読むべき記事
- Stream API基本
- 終端操作(collect・reduce)
タグ: Java, 中級者向け, 関数型プログラミング