【Java】Stream API - ストリーム終端処理

#ストリーム終端処理
* ストリーム処理で加工/フィルタされた値を最終的に出力したり集計する
* **ストリームは終端処理をトリガーにまとめて処理される**
* 終端処理は**省略不可**
    * cf:中間処理は省略可能
* 終端処理後にストリーム再利用は不可能（IllegalStateException）

##要素を順に処理
* **forEachメソッド**
* `System.out::println`メソッド参照をそのまま渡す以外にラムダ式で渡すこともできる

```java
import java.util.stream.Stream;
public class StreamForEach {
  public static void main(String[] args) {
    Stream.of("Munchkin", "Siamese", "Persian", "Tama")
    .forEach(v -> System.out.println(v));
  }
}
```

* forEachメソッドは**並列ストリームでは順序を保証しない！**
* →順番を守るには**forEachOrderedメソッド**を使う！

```java
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {
    Stream.of("Munchkin", "Siamese", "Persian", "Tama")
    .parallel()
    .forEach(v -> System.out.println(v));
    //Persian Tama Siamese Munchkin 
    
    /*順番を守りたい場合
    .forEachOrdered(v -> System.out.println(v)); 
    Munchkin Siamese Persian Tama
    */
  }
}
```

##最初の値を取得する
* **findFirstメソッド**
* ストリームがからである可能性もあるので、戻り値は**Optional型**

```java
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {

    var str = Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .filter(s -> s.startsWith("S"))
        .findFirst();
      //orElseメソッドでnullの場合"-"に置き換える
      System.out.println(str.orElse("－")); //Siamese
  }
}
```
* **findAnyメソッド**は並列ストリームで*得られた最初の結果を返す**ので結果が変わることも

```java
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {

    var str = Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .parallel()
    .filter(s -> s.startsWith("S"))
    .findAny();
     System.out.println(str.orElse("－")); //Scottish Fold
  }
}
```

##値が特定の条件を満たすか判定
* **anyMatch/allMatch/noneMatchメソッド**

```java
//リスト内の値が全部0以上か確認
import java.util.stream.IntStream;

public class StreamMatch {

  public static void main(String[] args) {
    System.out.println(
        IntStream.of(1, 10, 5, -5, 12)
          .allMatch(v -> v >= 0)
      ); //false
  }
}
```

##配列/コレクションに変換
* **toArrayメソッド**
* ストリーム処理結果を文字列配列として取得

```java
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    var list = Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .filter(s -> s.startsWith("S"))
        .toArray();
    System.out.println(list[0]); //Siamese
  }
}
```
* **collectメソッド**でコレクションに変換
* ストリーム処理結果をリストに変換
    * **toList**：リストへの変換
    * **toSet**：セットへの変換
    * **toMap**：マップへの変換
    * **toCollection**：一般的なコレクションへの変換


```java
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamTrans2 {

  public static void main(String[] args) {
    var list = Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .filter(s -> s.startsWith("S"))
        .collect(Collectors.toList());
    System.out.println(list);
  }
}
```

* **toMapメソッド**
    * 重複する可能性がある場合、merge引数を設定すること（IllegalStateException）

```java
//Personオブジェクト配列を名前:メールアドレスのマップ形式に変換
public class StreamCollectMap {

  public static void main(String[] args) {
    System.out.println(
        Stream.of(
          new Person("山田太郎", "tyamada@example.com"),
          new Person("鈴木花子", "hsuzuki@example.com"),
          new Person("井上三郎", "sinoue@example.com"),
          new Person("佐藤久美", "ksatou2@example.com"),
          new Person("山田太郎", "yamataro@example.com")
        ).collect(Collectors.toMap(
          //引数keyがマップのキーを表す
          Person::getName,
          //引数valueがマップのキーを表す
          Person::getEmail,
          //重複する可能性がある場合
          (s, a) -> s + "/" + a
          //重複時に値を上書き
          // (s, a) ->  a
        ))
      );
  }
}
```
```java
public class Person {
  private String name;
  private String email;
  public Person(String name, String email) {
    this.name = name;
    this.email = email;
  }
  public String getName() {
    return name;
  }
  public String getEmail() {
    return email;
  }
}
```

##最大、最小を求める
* **min.maxメソッド**
* 引数にComparator型
* naturalOrderで自然順ソート
* 戻り値はOptional型なので、値取り出しには**orElseメソッド**

```java
import java.util.Comparator;
import java.util.stream.Stream;

public class StreamMin {

  public static void main(String[] args) {
    var str = Stream.of("めばる", "さんま", "ひらめ", "いわし", "ほっけ")
        .min(Comparator.naturalOrder());
      System.out.println(str.orElse("")); //いわし
  }
}
```

##要素個数を求める
* **countメソッド**

```java
//文字列長3より大きい文字列の個数を求める
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    System.out.println(
    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
      .filter(s -> s.length() > 7)
      .count() //2
    );
  }
}
```

##合計/平均値を求める
* **sum/averageメソッド**
* 基本型ストリームでのみ使用可能
* averageメソッドの戻り値は**OptionalDouble型**
    * orElseメソッドでとり出す

```java
//int配列合計、平均を求める
import java.util.stream.IntStream;

public class StreamSum {

  public static void main(String[] args) {
    var list = new int[] { 5, 1, 10, -3 };
    System.out.println(IntStream.of(list).sum()); //13
    System.out.println(IntStream.of(list).average().orElse(0)); //3.25
  }
}
```

##ストリームの値を一個にまとめる
* **リダクション**
    * max/min/countメソッドもリダクション
* **reduceメソッド**でリダクションを汎用的に行う

###引数1個の場合：
* reduceメソッド（ラムダ式）が受け取る引数
    * **演算結果を格納するための変数**（result）
    * **個々の要素を受け取るための変数**（str）

```java
//文字列ストリームをカンマで区切りでまとめる
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {

    System.out.println(
      Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .sorted()
        .reduce((result, str) -> {
          return result + "," + str;
        })
        .orElse("") //Munchkin,Persian,Scottish Fold,Siamese,Tama
    );
  }
}
```

###引数2個の場合：
* 初期値を受け取ることができる
* 結果は非nullであるので非Optional
    * orElse経由なしでも値取得可能
* 並列ストリームでは分散された分、初期値がラムダ式に適用されるので結果が変化
    * 並列化した分重複する初期値が可能性がある

```java
import java.util.stream.Stream;
public class StreamReduce2 {
  public static void main(String[] args) {
    System.out.println(
      Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .sorted()
        // .parallel()
        .reduce("Himalayan", (result, str) -> {
          return result + "," + str;
        })
    ); //Himalayan,Munchkin,Persian,Scottish Fold,Siamese,Tama
  }
}
```

###引数3個の場合：
* Stream要素型と**最終結果型が異なる**場合に使用
* reduceで個々の要素を取得する際に演算結果(result)、個々の要素の変数(value)が**異なる型でもOK**
    * 引数result(Integer)に文字列valueを数値に変換したものを加算

```java
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    System.out.println(
      Stream.of("153", "211", "112", "350", "418", "208")
        .parallel()
        .reduce(0,
          //個々の要素を取得
          //演算結果をresultに格納、個々の要素をvalueが受け取る
          (result, value) -> {
            return result + Integer.parseInt(value); //文字列valueを数値に変換
          },
          //分散された結果をまとめる(並列ストリームのみ)
          (result1, result2) -> {
            return result1 + result2; //1452
          }
      )
    );
  }
}
```

##ストリーム要素をコレクションにまとめる（1）
* **collectメソッド**
* **可変リダクション**：コレクションやStringBuilderのように可変なコンテナ（入れ物）に値を蓄積して返す
    * cf: reduceはストリーム内要素をint/Stringのような**単一型にまとめる**

```java
//与えられた文字列ストリームをソートしてリストに変換
import java.util.ArrayList;
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    System.out.println(
      Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama"
        .sorted()
        .collect(
          //式数でリダクション最初の値に値を格納するコンテナ生成
          ArrayList<String>::new,
          //引数はコンテナ、個々の要素
          (list, str) -> list.add(str),
          //並列ストリームの場合
          (list1, list2) -> list1.addAll(list2)
        )
    );
  }
}
```

##ストリーム要素をコレクションにまとめる（2）
* **collectメソッドはオーバーロードを持つ**
* Collector.of静的メソッドで生成できる
* コレクター属性
    * **CONCURRENT**：マルチスレッドでaccumulator実行可能
    * **UNORDERED**：要素の順序を保証しない
    * **IDENTITY_FINISH**：finisherを省略可能
    
```java
import java.util.ArrayList;
//import java.util.Arrays;
import java.util.stream.Collector;
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    System.out.println(
      Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .sorted()
        .collect(
          Collector.of(
            ArrayList::new,
            ArrayList::add,
            (list1, list2) -> {
              list1.addAll(list2);
              return list1;
            },
            Collector.Characteristics.IDENTITY_FINISH
          )
        )
    );
  }
}
```

##標準コレクター
* Collectorsクラスにcollectメソッドで利用できるコレクターを生成するファクトリーメソッドがある
* **典型的なリダックション処理を簡単に実装！**
    * toList、toMapメソッドもファクトリーメソッドに含まれる

###**joiningメソッド**
* 文字列結合のコレクター生成

```java
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

  public static void main(String[] args) {
    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(Collectors.joining(",", "<", ">")) //<Munchkin,Persian,Scottish Fold,Siamese,Tama>
      );
  }
}
```

###**groupingByメソッド**
* 指定キーで値をグループ化するコレクター生成
 
```java
//文字列を長さ別に分類
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

  public static void main(String[] args) {
    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(
          //引数strで要素型を受け取りグループ化キーを生成
          //文字列単位でグループ化
            Collectors.groupingBy(str -> str.length()
          )) //{4=[Tama], 7=[Persian, Siamese], 8=[Munchkin], 13=[Scottish Fold]}
      );
  }
}
```

* **グループ化した結果を別のコレクターで処理**
    * joiningに引数指定

```java
public class Main {

  public static void main(String[] args) {
      
    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(
            Collectors.groupingBy(
              str -> str.length(),
              Collectors.joining("/") //{4=Tama, 7=Persian/Siamese, 8=Munchkin, 13=Scottish Fold}
          ))
      );
  }
}
```

###**partitioningByメソッド**
* **true/falseに分割するシンプルなグループ化**

```java
//文字列が7文字以内か長いかで分ける
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

  public static void main(String[] args) {
      
    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(
            Collectors.partitioningBy(
              str -> str.length() > 7
          ) //{false=[Persian, Siamese, Tama], true=[Munchkin, Scottish Fold]}
        )
      );
  }
}

```

###**collectingAndThenメソッド**
* コレクター実行後に終了処理実行
* コレクターと後処理を連結

```java
//toListメソッドでストリームをリスト化
//Collections::unmodifiableListで読み取り専用に変換

import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

  public static void main(String[] args) {
    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(
            Collectors.collectingAndThen(
              Collectors.toList(),
              Collections::unmodifiableList
            )
          )
      );
  }
}
```

###**IntSummaryStatisticsクラス**
* 数値の基本的な統計情報を取得
* collectメソッドの戻り値は(Int)SummaryStatisticsオブジェクト
* ゲッターメソッドで統計情報取得
    * **getAverage()**：平均値（値がない場合0）
    * **getCount()**：要素個数
    * **getMax()**：最大
    * **getMin()**：最小
    * **getSum()**：合計（値がない場合0）

```java
import java.util.IntSummaryStatistics;
import java.util.stream.IntStream;

public class Main {

  public static void main(String[] args) {
    var summary = IntStream.of(5, 13, 7, 2, 30)
      .collect(
     //コンストラクタ、accept、combineDBをparseIntソッドの参照を渡す
        IntSummaryStatistics::new,
        IntSummaryStatistics::accept,
        IntSummaryStatistics::combine
      );
    System.out.println(summary.getMin()); //2
    System.out.println(summary.getSum()); //57
    System.out.println(summary.getAverage()); //11.4
  }
}
```