Java8 で追加された Stream API は、これまでのコレクション操作を一変させるような大型のアップデートとなり、多くの Java エンジニアから歓迎されました。
Stream API とは
Stream API はコレクションから特定の条件でデータを抽出したり、コレクションの中身を別の型のコレクションに移し替えたり、合計値を求める時など、コレクションに対する操作を for 構文などの繰り返しを使わない形で値の集合を操作する APIです。
「中間操作」と「終端操作」
Stream API 操作は、生成、中間操作、終端操作の三段階に分けられます。
まずコレクションなどのデータソースから Stream の「生成」を行い、「中間操作」で条件による絞り込みや並び替えなどを経て、「終端操作」で最終的な処理結果を出力するのが基本的な流れです。
中間操作は複数指定することができますが、終端操作は一度の命令で1回しか実行できない特徴があるので覚えておきましょう。
Stream API のサンプルコード
Stream の生成?中間操作?や終端操作?と言われてもイメージが難しいかもしれないので、まずは簡単なサンプルコードを作って Stream API の実装イメージを見ていきましょう。
次のサンプルコードは、数値が10個入ったコレクションから、中間操作としてfilter メソッドで値が 5以上の要素を抽出し、終端操作で絞り込まれたデータを画面に出力しています。
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = Arrays.asList(2, 2, 3, 4, 5, 7, 3, 9, 1, 5);
list.stream()
.filter(i -> i >= 5) // 値が5以上のデータで絞り込み(中間操作)
.forEach(i -> System.out.println(i)); // 結果を出力(終端操作)
}
}
■ 実行結果
5
7
9
5
このように、Stream API を使用することで、直感的にコレクションに対する操作を行えるようになります。もし、上のコードを従来の for と if 構文で実装すると次のようなコードとなり、Stream API を使ったコードは、より直感的に操作が分かり易なっているかがお分かりになると思います。
public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(2, 2, 3, 4, 5, 7, 3, 9, 1, 5);
for (int i = 0; i < list.size(); i++) {
if (list.get(i) >= 5) {
System.out.println(list.get(i));
}
}
}
}
Stream APIが使用できるクラス
Stream API を使用する場合は、まずコレクションなどのオブジェクトから Stream のインスタンスを生成する必要があります。
Stream は、コレクションはもちろんのこと、文字列やファイルから読みっとった各行の内容から Stream を生成し操作することができます。
Stream を生成する代表的な方法は次のとおりです。
Colletion#stream()
Listなどのコレクションから Stream を生成します。最も多くのシーンで利用するケースでしょう。
List<Integer> list = Arrays.asList(1, 2, 3);
Stream stream = list.stream();
LIst 以外にも Map などの Key Value のコレクションからも Stream は生成できます。
Map<String, String> map = Map.of("key1", "値", "key2", "値");
Stream stream = map.entrySet().stream();
Arrays.stream(ary)
配列からも Stream クラスを生成できます。
int[] num = {4, 10, 7};
IntStream stream = Arrays.stream(num);
String#chars()、String#codePoints()
文字列を文字のリストとして Stream クラスを生成できます。
String str = "abcde";
IntStream stream = str.chars();
Files.lines()
ファイルの1行を1つの要素として Stream クラスを生成することもできます。
IntStream stream = FIles.lines(path).lines();
代表的な中間操作
中間操作は Stream の要素に対して操作を行い、中間操作では操作を適用後の新しいStreamインスタンスが返ります。そなため、中間操作はメソッドチェーンで処理を連結していくことができます。
よく使う代表的な中間操作には、次のものがあります。
filter
filter は、引数で指定した条件に一致する要素を抽出するメソッドです。
map
map は、Stream の要素を別の型に変換するメソッドです。
flatMap
flatMapは、mapと同じく Stream の要素を別の型に変換するメソッドです。map が1対1で要素の型を変換するのに対し、flatMap は1対多で変換をします。
つまり、入力した要素を複数の値に増殖するときなどに flatMap を使用します。
distinct
distinctは、Stream の要素の中で、値もしくわ指定したキーが重複している要素を取り除くメソッドです。
sorted
sortedは、Stream の要素を並び替えるメソッドです。
代表的な終端操作
Stream は、生成→中間操作を経て、最後は必ず終端操作を実施する必要があります。
よく使う代表的な終端操作には、次のものがあります。
collect
可変リダクション操作と呼ばれ、中間操作で抽出した一連の要素に特定の処理を適用して1つにまとめる操作をします。
主に、 Collections クラスを使って、List や HashSet などのコレクションクラス変換したりします。要素の値を1つの String の値に結合したりします。
count
その名の通り、Stream 要素の件数を取得するメソッドです。filter や flatMap などの要素数に変動を与えるような中間操作。行っている場合は、変動後の要素数が取得できます。
max / min
Stream の要素を比較して最大値・最小値を取得します。
まとめ
Stream API の基本的な操作な流れを解説してきました。 Stream API の登場により、これまで forやifの組み合わせ手によって複雑に行われていた操作も、シンプルなコードでまとめて記述することができるため、是非覚えておきましょう。
Stream API は Java8 で追加され Java のバージョンアップとともに進化を続けており、新しいバージョンの Java がリリースされるたびに便利な API が追加されていっています。