モダンなJavaの書き方②:Stream API

コレクション処理を簡潔に書ける Stream API の使い方を学びます。

モダン 1 2 3 4

モダンなJavaの書き方②:Stream API

Stream API とは

Stream API は Java 8 で導入された、コレクション(リストや配列など)のデータを宣言的に処理するための仕組みです。「何をしたいか」を直感的に記述でき、for ループで書いていた繰り返し処理を簡潔に表現できます。

Stream の処理は以下の3ステップで構成されます。

処理の流れ
① ストリームの生成 → ② 中間操作(0回以上) → ③ 終端操作(1回)
Stream は一方向に流れる「データのパイプライン」です。一度使ったストリームは再利用できません。また、終端操作が呼ばれるまで中間操作は実行されません(遅延評価)。

ストリームの生成

Stream は様々な方法で生成できます。

Java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

// List から生成
List<String> list = Arrays.asList("apple", "banana", "cherry");
Stream<String> s1 = list.stream();

// 配列から生成
String[] arr = {"red", "green", "blue"};
Stream<String> s2 = Arrays.stream(arr);

// 値を直接指定して生成
Stream<Integer> s3 = Stream.of(1, 2, 3, 4, 5);

中間操作

中間操作はストリームを変換・絞り込みする操作です。複数を連続して呼び出せます。

メソッド説明
filter(Predicate)条件に一致する要素だけに絞り込む
map(Function)各要素を別の値に変換する
sorted()要素を昇順に並べ替える
sorted(Comparator)指定の順序で並べ替える
distinct()重複を除去する
limit(n)先頭 n 件に絞り込む
skip(n)先頭 n 件をスキップする
peek(Consumer)要素を確認しながら通過させる(デバッグ用)

終端操作

終端操作はストリームから結果を取り出す操作です。呼び出すとストリームが消費されます。

メソッド説明
forEach(Consumer)各要素に処理を行う
collect(Collector)結果をコレクションに集める
count()要素数を返す
findFirst()最初の要素を Optional で返す
anyMatch(Predicate)1件でも条件を満たすか
allMatch(Predicate)全件が条件を満たすか
noneMatch(Predicate)1件も条件を満たさないか
reduce(BinaryOperator)要素を畳み込んで1つの値にする
min / max(Comparator)最小値 / 最大値を Optional で返す

filter と forEach

条件に合う要素だけ取り出して処理します。

Java(for ループ版)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// for ループで偶数だけ表示
for (int n : numbers) {
    if (n % 2 == 0) {
        System.out.print(n + " ");
    }
}
Java(Stream API 版)
import java.util.Arrays;
import java.util.List;

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Stream で偶数だけ表示
numbers.stream()
       .filter(n -> n % 2 == 0)
       .forEach(n -> System.out.print(n + " "));
実行結果
2 4 6 8 10

map による変換

各要素を別の値に変換します。

Java
import java.util.Arrays;
import java.util.List;

List<String> fruits = Arrays.asList("apple", "banana", "cherry");

// すべて大文字に変換して表示
fruits.stream()
      .map(s -> s.toUpperCase())
      .forEach(System.out::println);

// 文字数に変換して表示
fruits.stream()
      .map(s -> s + "(" + s.length() + "文字)")
      .forEach(System.out::println);
実行結果
APPLE
BANANA
CHERRY
apple(5文字)
banana(6文字)
cherry(6文字)

sorted と collect

並べ替えてからリストに収集します。

Java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 9, 2, 7);

// 昇順に並べ替えてリストに収集
List<Integer> sorted = numbers.stream()
                               .sorted()
                               .collect(Collectors.toList());
System.out.println(sorted);

// 5以上を降順で収集
List<Integer> filtered = numbers.stream()
                                  .filter(n -> n >= 5)
                                  .sorted((a, b) -> b - a)
                                  .collect(Collectors.toList());
System.out.println(filtered);
実行結果
[1, 2, 3, 5, 7, 8, 9]
[9, 8, 7, 5]

count・sum・average

集計処理もストリームで簡単に書けます。

Java
import java.util.Arrays;
import java.util.List;

List<Integer> numbers = Arrays.asList(10, 25, 8, 42, 17, 33);

// 30以上の要素数
long count = numbers.stream()
                    .filter(n -> n >= 30)
                    .count();
System.out.println("30以上の個数: " + count);

// 合計
int sum = numbers.stream()
                 .mapToInt(Integer::intValue)
                 .sum();
System.out.println("合計: " + sum);

// 平均
double avg = numbers.stream()
                    .mapToInt(Integer::intValue)
                    .average()
                    .orElse(0.0);
System.out.println("平均: " + avg);
実行結果
30以上の個数: 2
合計: 135
平均: 22.5

中間操作の連結

中間操作はメソッドチェーンで複数つなげられます。

Java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

List<String> words = Arrays.asList(
    "java", "stream", "lambda", "api", "java", "filter", "map"
);

// 重複を除いて、4文字以上の単語を大文字で取得
List<String> result = words.stream()
    .distinct()                          // 重複除去
    .filter(s -> s.length() >= 4)       // 4文字以上
    .map(String::toUpperCase)            // 大文字変換
    .sorted()                            // ソート
    .collect(Collectors.toList());       // リストに収集

System.out.println(result);
実行結果
[FILTER, JAVA, LAMBDA, STREAM]

reduce による畳み込み

全要素を組み合わせて1つの値にまとめます。

Java
import java.util.Arrays;
import java.util.List;

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

// 合計(reduce を使う場合)
int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);
System.out.println("合計: " + sum);

// 最大値
int max = numbers.stream()
                 .reduce(0, (a, b) -> a > b ? a : b);
System.out.println("最大値: " + max);

// 文字列を連結
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
String joined = fruits.stream()
                      .reduce("", (a, b) -> a.isEmpty() ? b : a + ", " + b);
System.out.println(joined);
実行結果
合計: 15
最大値: 5
apple, banana, cherry
まとめ
・Stream は「生成 → 中間操作 → 終端操作」の3ステップで処理する
filter で絞り込み、map で変換、sorted で並べ替え
collect でリストに収集、count / sum で集計
・中間操作は遅延評価されるため、終端操作が呼ばれて初めて実行される
・ラムダ式やメソッド参照と組み合わせることで処理を簡潔に記述できる