【Java】JavaのStream中間操作(filter・map・sorted)の使い方

Java

こんにちは、かつコーチです。
前回はStream APIの全体像を解説しましたが、今回は実務で最もよく使う中間操作(Streamを加工して次の処理に渡す操作)であるfiltermapsortedを掘り下げます。

中間操作とは?

中間操作の役割

中間操作はStreamを受け取り、加工した新しいStreamを返すメソッドです。
「中間」という名前の通り、それ単体では処理が実行されず、collectforEachなどの終端操作が呼ばれて初めて実行されます(遅延評価)。
複数の中間操作をつなげてパイプライン(処理の流れ)を作れる点が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()));
// キーボード
// モニター

filterPredicate<T>(trueかfalseを返す判定処理)を引数に取り、条件を満たす要素だけを残します。
複数条件を組み合わせたい場合は、前回解説したPredicateandorが活用できます。

map:要素を別の値に変換する

List<String> names = products.stream()
        .map(Product::name)
        .toList();

System.out.println(names); // [キーボード, マウス, モニター]

mapFunction<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); // [マウス, キーボード]

filtersortedmapのように複数の中間操作をつなげることで、「絞り込んで、並び替えて、名前だけ取り出す」という一連の処理を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は重複除去、skiplimitはページネーションのような「先頭からN件飛ばしてM件取る」処理に便利です。

まとめ

この記事のポイント

  • 中間操作はStreamを加工して次に渡す操作で、それ単体では実行されない(遅延評価)
  • filterは絞り込み、mapは変換、sortedは並び替えとそれぞれ役割が異なる
  • 処理コストを抑えるには、絞り込み(filter)を先に、変換(map)を後にするのが基本
  • distinctlimitskipも組み合わせるとさらに柔軟な処理が書ける

次に読むべき記事

  • Stream API基本
  • 終端操作(collect・reduce)

タグ: Java, 中級者向け, 関数型プログラミング

タイトルとURLをコピーしました