注目記事

【Javaコード入門】Stream API_④Streamの終端処理

終端処理では、Stream内で加工/フィルターされた値を最終的に出力/集計します。Streamでは、中間処理の時点では処理をストックします。そして、終端処理が呼び出されたところでまとめて加工/フィルターなどを実施します(遅延処理)。その性質上、中間処理は省略可能ですが、終端処理は末尾で必ず呼び出さなければなりません。

要素を順に処理する – forEachメソッド

public void forEach(Consumer<? super T> action)
 T:要素の型
 action:個々の要素を処理するラムダ式

たとえば以下は、Streamの内容を順に出力する例です。

StreamForEach.java
  1. package com.example.mynavi.streamapi;
  2.  
  3. import java.util.stream.Stream;
  4.  
  5. public class StreamForEach {
  6.   public static void main(String args) {
  7.     Stream.of("あいうえお", "かきくけこ", "さしすせそ")
  8.       .forEach(System.out::println);
  9.   }
  10. }

  1. あいうえお
  2. かきくけこ
  3. さしすせそ

forEachメソッドは、「個々の要素を引数として受け取り、これを処理する(戻り値はなし)」ラムダ式を受け取ります。printlnメソッドは、引数をひとつ受け取り、戻り値がvoidなので、そのままforEachメソッドに渡せるわけです(メソッドそのものを渡す表現を、メソッド参照と呼びます)。

よって、サンプルの太字部分は以下のように書き換えても同じ意味です。

  1. .forEach(s -> System.out.println(s));

Stream内の値が条件を満たすかを判定する – xxxxxMatchメソッド

public boolean anyMatch(Predicate<? super T> predicate)
public boolean allMatch(Predicate<? super T> predicate)
public boolean noneMatch(Predicate<? super T> predicate)
 T:要素の型
 predicate:条件式を表すラムダ式

anyMatchメソッドは条件式を満たす要素がひとつでもある場合、allMatchメソッドはすべての要素が条件式を満たす場合、そして、noneMatchメソッドはひとつも満たさない場合に、それぞれtrueを返します。

たとえば以下は、Stream内のすべての要素が5文字以上であるかを判定する例です。

StreamMatch.java
  1. package com.example.mynavi.streamapi;
  2.  
  3. import java.util.stream.Stream;
  4.  
  5. public class StreamMatch {
  6.  
  7.   public static void main(String args) {
  8.     System.out.println(
  9.         Stream.of("あかさか", "さくらぎちょう", "よよぎ")
  10.         .allMatch(v -> v.length() >= 5)
  11.     );  // 結果:false
  12.   }
  13. }

太字の部分をanyMatch/noneMatchに変更した場合には、それぞれ結果はtrue、falseとなります。

最初の要素だけを取得する – findFirstメソッド

public Optional<T> findFirst()
 T:要素の型

findFirstメソッドを利用することで、Streamから先頭の要素だけを取得できます。

streamFirst.java
  1. package com.example.mynavi.streamapi;
  2.  
  3. import java.util.stream.Stream;
  4.  
  5. public class streamFirst {
  6.  
  7.   public static void main(String args) {
  8.     System.out.println(
  9.       Stream.of("あかさか", "さくま", "あおし")
  10.         .filter(s -> s.startsWith("あ"))
  11.         .findFirst()
  12.         .orElse("×")
  13.     );  // 結果:あかさか
  14.   }
  15. }

findFirstメソッドの戻り値は、Optional型です。よって、値を取得するにはorElseなどのメソッドを介さなければなりません。

Streamの内容を集計する

public Optional<T> max(Comparator<? super T> comparator) … 最大値
public Optional<T> min(Comparator<? super T> comparator) … 最小値
public long count() … 個数
public OptionalDouble average() … 平均値
public int sum() … 合計値
 T:要素の型
 comparator:比較ルールを表すラムダ式

Streamの内容を集計するために、上のようなメソッドを利用できます。ただし、average/sumメソッドが利用できるのは、IntStreamなどの基本型Streamだけです。

たとえば以下は、これらのメソッドを使って数値型Streamの内容を集計する例です。

StreamSum.java
  1. IntStream stream =IntStream.of(15, 7, 38, 50, 1);
  2. System.out.printf("最大値:%d\n", stream.max().orElse(0));
  3. //System.out.printf("最小値:%d\n", stream.min().orElse(0));
  4. //System.out.printf("個数:%d\n", stream.count());
  5. //System.out.printf("平均値:%f\n", stream.average().orElse(0));
  6. //System.out.printf("合計値:%d", stream.sum());

  1. 最大値:50

結果は1行ずつコメントアウト、コメントを解除した上で確認してください(一旦、終端処理を呼び出したStreamに対して繰り返し終端処理を呼び出すことはできません)。

集計値をまとめて取得したいならば、XxxxxSummaryStatisticsクラスを利用することもできます(XxxxxはInt、Long、Doubleのいずれか)。

StreamStatistics.java
  1. package com.example.mynavi.streamapi;
  2.  
  3. import java.util.IntSummaryStatistics;
  4. import java.util.stream.IntStream;
  5.  
  6. public class StreamSum {
  7.  
  8.   public static void main(String args) {
  9.     var summary = IntStream.of(15, 7, 38, 50, 1).collect(
  10.           IntSummaryStatistics::new,
  11.           IntSummaryStatistics::accept,
  12.           IntSummaryStatistics::combine
  13.       );
  14.  
  15.     System.out.printf("最大値:%d\n", summary.getMax());
  16.     System.out.printf("最小値:%d\n", summary.getMin());
  17.     System.out.printf("個数:%d\n", summary.getCount());
  18.     System.out.printf("平均値:%f\n", summary.getAverage());
  19.     System.out.printf("合計値:%d", summary.getSum());
  20.   }
  21. }

  1. 最大値:50
  2. 最小値:1
  3. 個数:5
  4. 平均値:22.200000
  5. 合計値:111

collectメソッドは、Streamの内容を束ねるための機能を提供します。独自のラムダ式を渡すこともできますが、数値集計をするならば、太字の記述はイディオムと考えてよいでしょう。

Streamの値をまとめる

public Optional<T> reduce(BinaryOperator<T> accumulator)
 T:要素の型

accumulator:値を結合するためのラムダ式
reduceメソッドを利用することで、Streamの内容を独自の規則で束ねることができます。たとえば以下は、String型のStreamをタブ区切りでまとめる例です。

StreamReduce.java
  1. package com.example.mynavi.streamapi;
  2.  
  3. import java.util.stream.Stream;
  4.  
  5. public class StreamReduce {
  6.  
  7.   public static void main(String[] args) {
  8.     System.out.println(
  9.       Stream.of("あかさか", "さくらぎちょう", "よよぎ")
  10.         .reduce((result, str) -> result + "\t" + str)
  11.         .orElse("")
  12.     );  // 結果:あかさか  さくらぎちょう よよぎ
  13.   }
  14. }

引数accumulatorは、引数として

  • 演算結果を格納するための変数(result。初期値は先頭の要素)
  • 個々の要素を受け取る変数(str)

を受け取るラムダ式です。引数resultの内容はStreamを通じて引き継がれるので、この例であれば、Streamの内容を順に「<タブ>, 要素値」で連結していくという意味になります。

Stream API_目次
Stream APIの基本
Streamの生成
Streamによる中間処理
Streamの終端処理

監修

山田祥寛(有限会社 WINGSプロジェクト)
静岡県榛原町生まれ。一橋大学経済学部卒業後、NEC にてシステム企画業務に携わるが、2003年 4 月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト(https://wings.msn.to/)」の代表でもある。主な著書に『改訂 3 版JavaScript 本格入門』(技術評論社)、「独習シリーズ(C#・Python・PHP・Ruby・JSP&サーブレットなど)」(翔泳社)、「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravel など)」(Amazon Kindle)など。売り上げの累計は100万部を超える。