Java
Z
信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
Java Stream APIを完全活用する — filter/map/collectから並列処理まで
Java 8で導入されたStream APIの基本操作から、collectors、flatMap、並列ストリームまで実例で解説します。
一言結論
Stream APIはループを書く代わりにfilter/map/collectで処理を宣言的に記述でき、並列化も.parallelStream()一行で試せるが、小さいコレクションではオーバーヘッドが上回るため計測して使うこと。
Stream API とは
コレクションのデータを宣言的に処理するAPIです。ループを書かずに変換・フィルタ・集計が書けます。
// 従来のforループ
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("A")) {
result.add(name.toUpperCase());
}
}
// Stream API
List<String> result = names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.collect(Collectors.toList());
Streamの作成
// コレクションから
List<String> list = List.of("a", "b", "c");
Stream<String> stream = list.stream();
// 配列から
String[] array = {"x", "y", "z"};
Stream<String> stream = Arrays.stream(array);
// 直接指定
Stream<String> stream = Stream.of("a", "b", "c");
// 範囲(IntStream)
IntStream.range(0, 10) // 0〜9
IntStream.rangeClosed(1, 10) // 1〜10
中間操作(Intermediate Operations)
中間操作は 遅延評価 されます(終端操作が呼ばれるまで実行されない)。
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// filter: 条件に合うものだけ残す
numbers.stream().filter(n -> n % 2 == 0) // [2, 4, 6, 8, 10]
// map: 各要素を変換
numbers.stream().map(n -> n * n) // [1, 4, 9, 16, 25, ...]
// sorted: ソート
numbers.stream().sorted(Comparator.reverseOrder())
// distinct: 重複除去
Stream.of(1, 2, 2, 3, 3).distinct() // [1, 2, 3]
// limit / skip
numbers.stream().skip(3).limit(5) // [4, 5, 6, 7, 8]
// peek: デバッグ用(値を確認しながら流す)
numbers.stream()
.filter(n -> n > 5)
.peek(n -> System.out.println("filtered: " + n))
.map(n -> n * 2)
.collect(Collectors.toList());
flatMap: ネストを平坦化
List<List<Integer>> nested = List.of(
List.of(1, 2, 3),
List.of(4, 5),
List.of(6, 7, 8, 9)
);
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
終端操作(Terminal Operations)
List<String> names = List.of("Alice", "Bob", "Charlie", "Dave");
// collect: リストやマップに変換
List<String> list = names.stream().collect(Collectors.toList());
// count
long count = names.stream().filter(n -> n.length() > 3).count();
// findFirst / findAny
Optional<String> first = names.stream()
.filter(n -> n.startsWith("C"))
.findFirst();
// anyMatch / allMatch / noneMatch
boolean anyLong = names.stream().anyMatch(n -> n.length() > 5);
boolean allShort = names.stream().allMatch(n -> n.length() < 10);
// reduce: 畳み込み
int sum = IntStream.rangeClosed(1, 10).reduce(0, Integer::sum);
Collectors の活用
List<Person> people = ...;
// グルーピング
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
// カウント
Map<String, Long> countByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));
// 文字列結合
String joined = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
// [Alice, Bob, Charlie, Dave]
// 統計
IntSummaryStatistics stats = people.stream()
.mapToInt(Person::getAge)
.summaryStatistics();
System.out.println("平均: " + stats.getAverage());
System.out.println("最大: " + stats.getMax());
並列ストリーム
// parallelStream() で並列処理
long count = names.parallelStream()
.filter(n -> n.length() > 3)
.count();
注意: 並列ストリームは常に速いわけではありません。
- 要素数が少ない場合はオーバーヘッドで逆に遅い
- 順序に依存する処理は注意が必要
- スレッドセーフでない操作(
ArrayListへの追加など)はNG
よくある間違い
// NG: Streamは一度しか使えない
Stream<String> stream = names.stream();
stream.filter(...).collect(...);
stream.map(...); // IllegalStateException: stream has already been operated upon or closed
// NG: forEach で外部変数を変更する
List<String> result = new ArrayList<>();
names.stream().forEach(n -> result.add(n)); // 並列時に危険
// OK: collect を使う
List<String> result = names.stream().collect(Collectors.toList());
まとめ
| 操作 | メソッド | 説明 |
|---|---|---|
| 中間 | filter | 条件で絞り込む |
| 中間 | map | 各要素を変換 |
| 中間 | flatMap | ネストを平坦化 |
| 中間 | sorted | ソート |
| 終端 | collect | コレクションに変換 |
| 終端 | reduce | 畳み込み |
| 終端 | count/sum | 集計 |
Stream API はコードの意図を明確に表現できます。まず filter + map + collect の組み合わせから慣れていきましょう。