導入
JavaのStream API(ストリームAPI)は、コレクションなどのデータソースに対してパイプライン処理を行うための強力な機能を提供してくれます。これを使うことで、データを連続的に処理しながら、各ステップを直感的に記述できます。
本記事では、Stream APIの基本を入門レベルでわかりやすく解説します。
Stream APIの概要
パイプライン処理
Stream APIのメインになるのはパイプライン処理です。
パイプライン処理とは、ある処理の出力が次の処理の入力
となり、一連の処理が連鎖的に行われる仕組みです。
例えば、データをフィルタリングしてから、変換し、最後に集計する、といった一連の処理をパイプラインとして記述できます。
ストリームの基本構造
ストリームの処理は次の3段階に分かれます。
- ストリーム生成
- 中間操作
- 終端操作
以下の例ではこれらの3段階のステップが実行されています。
List<Integer> list = Arrays.asList(10, 3, 1, 8, 3, 2, 6, 7, 10, 6);
long count = list.stream() // → ストリーム生成
.filter(x -> x % 2 == 0) // → 中間操作1: 偶数のみ抽出 (10, 8, 2, 6, 10, 6)
.distinct() // → 中間操作2: 重複排除 (10, 8, 2, 6)
.count(); // → 終端操作: 要素の個数をカウント
System.out.println(count);
// [4]
上記のように1つの処理の中で複数のステップを実行していることがわかると思います。
ステップ1: ストリームの生成
ストリームは、コレクションや配列など、さまざまなデータソースから生成できます。
ストリームの生成には、以下の方法があります。
配列からのストリーム生成
配列をストリームにする際はArrays.stream
メソッドを使います。
String[] strArr = {"aaa", "bbb", "ccc"};
Arrays.stream(strArr) // 配列からストリームを生成
.forEach(System.out::println);
コレクションからのストリーム生成
コレクションをストリームにする際はCollection.stream
メソッドを使います。
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numList.stream() // コレクションからストリームを生成
.forEach(System.out::println);
Stream
クラスの静的メソッド
その他のストリーム生成の方法です。
Stream.of
、Stream.generate
、Stream.iterate
、Stream.concat
などの静的メソッドを使ってストリームを生成することができます。
// 単一要素から生成
Stream<String> singleElementStream = Stream.of("apple");
// 無限ストリームの生成
Stream<Double> randomNumbers = Stream.generate(Math::random)
.limit(5); // 制限なくストリームを生成し、5つに切り抜く
randomNumbers.forEach(System.out::println);
プリミティブ型ストリーム
Intなどのプリミティブ型に対しては、IntStream.range
とIntStream.rangeClosed
などを使って整数の範囲を指定してストリームを生成できます。
IntStream.rangeClosed(1, 5)
.forEach(System.out::println);
// [1, 2, 3, 4, 5]
ステップ2: 中間操作
中間操作はストリームを変換するステップです。複数回実行することが可能です。
代表的な中間操作を見てみましょう。
filter
指定した条件に合致する要素だけを含むストリームを生成する中間操作です。
List<String> list = Arrays.asList("apple", "banana", "cherry", "blueberry");
Stream<String> filteredStream = list.stream()
.filter(s -> s.startsWith("b")); // (banana, blueberryのみにフィルタ)
map
各要素に対して何らかの処理を施し、その結果を新しいストリームとして返す中間操作です。
List<String> list = Arrays.asList("apple", "banana", "cherry");
Stream<String> upperCaseStream = list.stream()
.map(String::toUpperCase); // 全て大文字に変換する
distinct
重複する要素を除いたストリームを生成する中間操作です。
List<Integer> list = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
Stream<Integer> distinctStream = list.stream()
.distinct(); // (1, 2, 3, 4, 5)
ステップ3: 終端操作
終端操作はストリームのデータに対して最終的な処理を行います。
終端操作はストリームの処理を完了し、結果を生成するため、一連の処理の中で生成されたストリームは再利用できなくなります。
forEach
各要素に対してアクションを実行します。
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.stream()
.forEach(System.out::println);
// apple
// banana
// cherry
collect
ストリームの要素をコレクションなどに変換します。
List<String> list = Arrays.asList("apple", "banana", "cherry", "blueberry");
List<String> resultList = list.stream()
.filter(s -> s.startsWith("b"))
.collect(Collectors.toList());
// resultListは["banana", "blueberry"]のリストになる
count
ストリーム内の要素の個数を返します。
List<String> list = Arrays.asList("apple", "banana", "cherry");
long count = list.stream()
.count();
// [3]
並列ストリーム
順次ストリームと並列ストリーム
- 順次ストリーム: 要素を1つずつ順番に処理するストリームです。元のリストの順序が保持されます。
- 並列ストリーム: マルチスレッドで要素を並列に処理するストリームです。処理順序は保証されませんが、大規模データの処理に有利です。
順次ストリームの例
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream()
.forEach(System.out::println);
// 順番に出力 1, 2, 3, 4, 5
並列ストリームの例
並列ストリームを生成するには、parallelStream
メソッドを使用します。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.parallelStream()
.forEach(System.out::println);
// 順番は不定 2, 4, 5, 3, 1 など
終わりに
この記事では、JavaのStream APIを使ったパイプライン処理の基本を紹介しました。ストリームを使えば、データの処理が簡潔かつ効率的に記述できます。
是非参考にしてみてください。