見出し画像

[初学者向け]Java8 Stream APIの使い方(後編)

本記事は「Java Advent Calendar 2023」の参加記事です。

こんにちは。システム開発本部の佐々木です。
先週かかった胃腸炎の名残でお腹がゆるゆるなエブリデイです。

前回Streamの中間操作についてゆるくまとめたので、今回はその続編として終端操作についても紹介させて頂きます。

前回のおさらいとして、Streamとして流れてきたデータを受け取ってごにょごにょして、そしてまたStreamとして後続に流していく。おおまかにそういった操作を中間操作というのでした。

今回紹介する終端操作とは名の通りStreamという流れを終結させるような操作を指します。
Streamとして流れているデータ達を最終的にどうするのか。コレクションとしてまとめたり、あるいは間引きされず流れてきたデータがいくつあるのかカウントするだけであったり、場面によってやりたい操作は色々あると思います。
今回も例によって、私が普段よく使うものをピックアップしていくつか挙げ連ねていきます。

1. collect

間違いなく一番使う頻度が高いのはこのcollectメソッドです。
今回詳細は割愛しますが、collectメソッドは引数次第でデータを様々な形で集約することができます。
基本は以下のような形で使います。

Stream.of(1, 2, 3, 4, 5)
    .filter(num -> num % 2 == 0)
    .collect(Collectors.toList()); // 偶数のリスト([2, 4])が取得される

2. count

文字通り、数を数える終端操作です。
主にfilterで特定条件下で絞り込みをして、最終的にいくつの要素が残ったかを取得したい際に使います。
ちなみに戻り値はintではなくlongです。

Stream.of(1, 2, 3, 4, 5)
    .filter(num -> num % 2 == 0)
    .count(); // 偶数の数(2)が取得される

3. reduce

reduceは流れてきたデータを累積しながら何かしらの操作を行うことができます。よくあるのはシンプルに値を次々足していって合計値を取得する使い方です。
reduceも引数が何種類かありますが、最もよく目にするのは第一引数に初期値、第二引数に累積関数(accumulator)を受け取る形です。例を見た方が明快かと思われますので以下に示します。

Stream.of(1, 2, 3, 4, 5)
    .reduce(100, (a, b) -> a + b);

// 初期値を100とし、累積していってる側(a)と流れてくる側(b)を加算している。
// 100+1, 101+2, 103+3, 106+4, 110+5を順に行っている。
// 取得される値は115

4. any/all/noneMatch

最後4つ目は3点欲張りセット。いずれも引数はPredicateで戻り値はbooleanです。
用途としては、特定の条件でfilterしたのちの存在確認で使うことが多いです。

anyMatch
流れてくるデータを1つ1つ見ていって、引数のPredicateを満たすものが1つでもあると即trueを返します。Streamとして1つもデータが流れてこない場合falseを返す仕様となっています。

allMatch
流れてくるデータを全てチェックし、全てがPredicateを満たしているかどうかの判定を返します。Streamとして1つもデータが流れてこない場合trueを返す仕様となっています。

noneMatch
流れてくるデータを全てチェックし、全てがPredicateを満たしていないかどうかの判定を返します。Streamとして1つもデータが流れてこない場合trueを返す仕様となっています。

Stream.of(1, 2, 3, 4, 5)
    .anyMatch(num -> num % 2 == 0); // true

Stream.of(1, 2, 3, 4, 5)
    .filter(num -> false) // データが1つも流れない場合
    .anyMatch(num -> num % 2 == 0); // false

Stream.of(1, 2, 3, 4, 5)
    .allMatch(num -> num % 2 == 0); // false

Stream.of(2, 4, 6, 8, 10)
    .allMatch(num -> num % 2 == 0); // true

Stream.of(1, 2, 3, 4, 5)
    .filter(num -> false) // データが1つも流れない場合
    .allMatch(num -> num % 2 == 0); // true

Stream.of(1, 2, 3, 4, 5)
    .noneMatch(num -> num % 2 == 0); // false

Stream.of(1, 3, 5, 7, 9)
    .noneMatch(num -> num % 2 == 0); // true

Stream.of(1, 2, 3, 4, 5)
    .filter(num -> false) // データが1つも流れない場合
    .noneMatch(num -> num % 2 == 0); // true


おわりに

前回と今回合わせてStream API の基本となる中間操作と終端操作の使い方がなんとなく分かったかと思います。
「中間操作と終端操作」において大事な約束事を1つ伝え忘れていました。Streamの一連の処理はいくら中間操作をモリモリに書き連ねたとしても、終端操作を呼ばない限りはその中間操作すらも動きません。

Stream<Integer> stream = Stream.of(1,2,3,4,5)
    .filter(num -> num % 2 == 0)
    .peek(num -> System.out.println(num));

上記コードの場合、コンソールには何も出力されません。

Stream<Integer> stream = Stream.of(1,2,3,4,5)
    .filter(num -> num % 2 == 0)
    .peek(num -> System.out.println(num));
long evenCount = stream.count();

このように終端操作(count)を呼び出して初めてfilterやpeekなどの中間処理も動き出し、コンソールに2と4が出力されます。

この簡単便利なStream APIという存在を頭の片隅に置いて、ここぞという場面で実践できるようになればあなたも胸を張って「Stream使いこなせてます!」と言えるでしょう。

この記事が気に入ったらサポートをしてみませんか?