Java | Java 標準ライブラリ:Stream API 概要

Java Java
スポンサーリンク

Stream API をざっくりイメージする

まず感覚から先に。

Stream API は、

「配列やコレクションなど“データの列”に対して、
 “何をしたいか”を宣言的に書けるパイプライン」

です。

for 文で

  1. フィルタする(条件に合うものだけ残す)
  2. 値を変換する
  3. 合計を出す/集計する

という処理をゴリゴリ書いていたところを、
filtermapsum などの“つなげ書き”で表現できます。

イメージとしては

「データの流れ(ストリーム)に対して
 “こう流して、こう加工して、最後にこう集計”と指示する」

感じです。


最初の一歩:for 文で書いていた処理を Stream にしてみる

例題:リストから偶数だけ抜き出して合計する

まずは従来の for 文で書いてみます。

import java.util.*;

public class ForLoopExample {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);

        int sum = 0;
        for (int n : list) {
            if (n % 2 == 0) {      // 偶数だけ
                sum += n;          // 合計
            }
        }

        System.out.println(sum);   // 12
    }
}
Java

これを Stream API で書くと、こうなります。

import java.util.*;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);

        int sum = list.stream()          // ストリームを作る
                      .filter(n -> n % 2 == 0)  // 偶数だけに絞る
                      .mapToInt(n -> n)         // int ストリームに変換
                      .sum();                   // 合計

        System.out.println(sum); // 12
    }
}
Java

何をしているかを日本語にすると、

  1. list.stream()
    リストから「ストリーム(データの流れ)」を作る
  2. .filter(n -> n % 2 == 0)
    条件に合う要素だけ通す(偶数だけ残す)
  3. .mapToInt(n -> n)
    IntStream(int 専用のストリーム)に変換
  4. .sum()
    流れてきた値を全部足す

というパイプラインになっています。

「何をしたいか」が左から右に素直に読めるのが Stream の一番の強みです。


Stream の基本構造:「中間操作」と「終端操作」

大きな流れをつかむ

Stream API は、ざっくり次の 3 ステップでできています。

  1. ストリームを「生成」する
  2. ストリームを「変形・フィルタ」する(中間操作)
  3. 最後に「結果を取り出す」(終端操作)

ここが分かると、かなり怖くなくなります。

1. ストリームの生成

代表的なパターンは次のようなものです。

list.stream()
Arrays.stream(array)
Stream.of(1, 2, 3)

例えば List からストリームを作るとき:

List<String> names = Arrays.asList("Alice", "Bob", "Carol");
names.stream()       // ここで Stream<String> ができる
Java

ここまでは「まだ何も実行されていない」とイメージしてください。
「これからこういう処理をするための準備」をしている段階です。

2. 中間操作(filter / map など)

中間操作は、「新しいストリームを返す操作」です。
代表的なものに filtermapsorted などがあります。

例えば:

names.stream()
     .filter(name -> name.length() <= 4) // 4文字以下
     .map(name -> name.toUpperCase())    // 大文字に変換
Java

ここまで書いても、まだ何も実行されません。
「こういうパイプラインを組み立てた」だけの状態です。

3. 終端操作(collect / forEach / count など)

最後に「終端操作」を呼ぶと、一気に実行されます。
代表例は collectforEachcountsum などです。

List<String> shortUpperNames =
    names.stream()
         .filter(name -> name.length() <= 4)
         .map(name -> name.toUpperCase())
         .toList();   // Java 16 以降の便利メソッド
Java

toList() が終端操作にあたり、
ここで初めて「ストリームが消費されて」「結果の List ができる」という流れになります。

この「終端操作を呼ぶまで実行されない」性質を、遅延評価 と呼びます。


よく使う中間操作をイメージでつかむ

filter:条件に合うものだけ通す

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> evens =
    list.stream()
        .filter(n -> n % 2 == 0)
        .toList();  // [2, 4]
Java

「if で弾く」のではなく、「条件に合うものだけ流す」イメージです。

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

List<String> names = Arrays.asList("alice", "bob", "carol");

List<String> upper =
    names.stream()
         .map(s -> s.toUpperCase())
         .toList();  // ["ALICE", "BOB", "CAROL"]
Java

map は「1 つの要素を別の値に変えて流す」操作です。
for 文で書いていた「新しいリストに add していく」作業をまとめて表現できます。

sorted:並び替える

List<String> sorted =
    names.stream()
         .sorted()                     // 自然順序でソート
         .toList();
Java

sorted(comparator) を使えば、Comparator で自由に順序も指定できます。


よく使う終端操作をイメージでつかむ

collect / toList:結果をコレクションに集める

Java 16 以降なら toList() が直感的です。

List<String> result =
    names.stream()
         .filter(name -> name.length() <= 3)
         .toList();
Java

Java 8〜15 では Collectors.toList() を使います。

import java.util.stream.Collectors;

List<String> result =
    names.stream()
         .filter(name -> name.length() <= 3)
         .collect(Collectors.toList());
Java

forEach:1 つずつ処理をする

names.stream()
     .filter(name -> name.startsWith("A"))
     .forEach(System.out::println);
Java

forEach は「結果をどこかに集めはしないが、副作用(表示、書き込みなど)を発生させる」終端操作です。

count / sum / average:集計系

long count =
    names.stream()
         .filter(name -> name.length() <= 3)
         .count();
Java

数値系は、mapToInt / mapToLong / mapToDouble を組み合わせることが多いです。

int totalLength =
    names.stream()
         .mapToInt(String::length)
         .sum();
Java

Stream API をいつ使うか・いつ使わないか

向いている場面

Stream が特に力を発揮するのは、

「コレクションや配列に対して、“フィルタ → 変換 → 集計” のような処理をする」場面です。

たとえば:

・条件に合う要素だけ抽出して、新しい List を作りたい
・あるプロパティだけ抜き出して、別の形の List にしたい
・条件を満たす件数を数えたい
・グルーピング、集計、最小値・最大値などを出したい

こういう処理は、for 文で書くより Stream で書いた方が
「何をしたいか」が一目で分かるコードになりやすいです。

まだ for 文で書いた方が分かりやすい場面

一方で、次のような場合は、無理に Stream にすると逆に読みにくくなりがちです。

ループの中で複雑な状態を更新する
ネストしたループや、早期 return / break がたくさん出てくる
「1 回だけ処理したい」ような単純なループ

初心者のうちは、

まず for 文で素直に書いてみる
→「これ Stream で書いたらスッキリしそう」と感じる箇所だけ、少しずつ置き換える

くらいのペースで十分です。


まとめ:Stream API の全体像を頭に定着させる

Stream API を、あらためてシンプルに整理するとこうです。

コレクションや配列など“要素の並び”に対して、
「何をしたいか」を filter / map / sorted / collect などの
“処理をつなげたパイプライン”として書く仕組み。

重要な感覚はこのあたりです。

  • list.stream() でストリームを生成
  • filter / map / sorted などで「中間操作」をつなげていく
  • collect / toList / forEach / count / sum などの「終端操作」を呼んだ瞬間に一気に実行される(遅延評価)
  • for 文でやっていた「フィルタ → 変換 → 集計」のような処理を、宣言的に、短く、読みやすく書ける

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