Stream API: 関数型データ処理
filter/map/reduce でコレクションを宣言的に処理。
なぜStream APIが必要か?
Stream APIは、コレクション(List、Setなど)を宣言的に処理するためのAPIです。従来のforループを使った命令的なコードでは、処理の意図が読み取りにくくなりがちです。Stream APIを使うと、filter(フィルタリング)、map(変換)、reduce(集約)などの操作を連鎖させて、処理の意図を明確に記述できます。また、並列処理(parallelStream)を簡単に利用できるため、大量データの処理も効率的に行えます。
いつ使うか?
Stream APIは、以下のような場面で使います: 1. **コレクションをフィルタリングしたいとき**: 特定の条件に合う要素だけを抽出したいとき。 2. **コレクションを変換したいとき**: 各要素を別の形式に変換したいとき。 3. **コレクションを集約したいとき**: 要素を1つの値にまとめたいとき(合計、平均など)。 4. **大量データを並列処理したいとき**: データ量が多く、処理時間を短縮したいとき。
実践テクニック
ストリームは一度しか使えない
Streamは「消費型」で、一度しか使えません。同じStreamを2回以上使おうと、IllegalStateExceptionが発生します。再利用したい場合は、toList()などでコレクションに変換してから、再度stream()を呼び出す必要があります。
Optionalと連携する
Streamの終端操作(findFirst、findAnyなど)はOptionalを返します。Optionalはnullチェックを強制できるため、より安全なコードになります。orElse()やorElseThrow()を使って、値がない場合のデフォルト値や例外処理を明示的に記述できます。
並列処理を慎重に使う
parallelStream()を使うと、並列処理が可能になりますが、スレッドセーフティを考慮する必要があります。共有リソースへのアクセスは適切に同期し、データ競合を防ぐ必要があります。また、並列処理のオーバーヘッドが大きいため、小規模データでは逆に遅くなる可能性があります。
Stream API
import java.util.List;import java.util.stream.*;
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// filter: 条件に合う要素を抽出List<Integer> evens = numbers.stream() .filter(n -> n % 2 == 0) .toList(); // [2, 4, 6, 8, 10]
// map: 各要素を変換List<Integer> doubled = numbers.stream() .map(n -> n * 2) .toList(); // [2, 4, 6, ..., 20]
// reduce: 集約int sum = numbers.stream() .reduce(0, Integer::sum); // 55
// 連鎖double average = numbers.stream() .filter(n -> n > 5) .mapToInt(Integer::intValue) .average() .orElse(0.0); // 8.0[2, 4, 6, 8, 10]\n55\n8.0
// ❌ Bad: 長いforループList<String> result = new ArrayList<>();for (User u : users) { if (u.getAge() > 20) { result.add(u.getName().toUpperCase()); }}// ✅ Good: Stream で宣言的にList<String> result = users.stream() .filter(u -> u.getAge() > 20) .map(u -> u.getName().toUpperCase()) .toList();パターン
// collect: 様々な形式に変換Map<String, Integer> map = users.stream() .collect(Collectors.toMap( User::getName, User::getAge ));
// グルーピングMap<String, List<User>> byDept = users.stream() .collect(Collectors.groupingBy(User::getDepartment));
// flatMap: ネストを平坦化List<String> allTags = articles.stream() .flatMap(a -> a.getTags().stream()) .distinct() .toList();
// 並列処理long count = numbers.parallelStream() .filter(n -> isPrime(n)) .count();
// Optional との連携users.stream() .filter(u -> u.getId() == targetId) .findFirst() .orElseThrow(() -> new UserNotFoundException(targetId));