[Java] Stream API 入門

#Stream API #とは
[こちら](https://qiita.com/xrdnk/items/7fcc349929685f029440)の記事の続きみたいなものです．

Stream API はDataをPipeLine形式で処理するためのAPIです．
Collection，配列，ファイルなどデータの集合体(Data Source)から，個々の要素を取り出して
これを「処理の流れ」(Stream)に引き渡すための仕組みを提供します．

Streamに対して関数操作を行った結果をStreamで返す「中間操作」と
処理結果をDataとして返す「終端操作」があります．

中間操作も終端操作もメソッド引数は関数型インターフェースを取るものが多いため，
ここでラムダ式の知識が利用していくとスマートということになります．
![Stream API.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156186/f44579d8-ebab-2013-b88a-1a6de6a1b3bd.png)

#Stream…？I/O Streamとは違う？
Javaにはjava.ioパッケージにI/O Streamが提供されていますが，
こちらのStreamの意味は入出力をStreamになぞらえた概念ですが，
Stream APIのStreamはDataのPipeLine処理をStreamに見立てた概念です．

#Stream API の基本
```java:
import java.util.ArrayList;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {

        // ①Data Sourceを準備する
        var list = new ArrayList<String>(
        		Arrays.asList("tokyo", "nagoya", "osaka", "fukuoka", "hokkaido", "okinawa"));

        // ②ストリームを作る
        list.stream().
            filter(s -> s.length() > 5).         // ③中間処理を行う
            map(String::toUpperCase).
            forEach(System.out::println);        // ④終端処理を行う

        // 結果：NAGOYA FUKUOKA HOKKAIDO OKINAWA
    }
}
```
Stream APIの処理は以下で構成されます．
**①Data Source準備する→②Stream生成→③抽出/加工などの中間処理→④出力/集計などの終端処理**

上の例でいえば，
①まずArrayListのData Sourceを作る

②Streamを生成する．ここではArrayList\<String>をもとにしているため，
　Streamメソッドも，Stream\<String>オブジェクトを返します．

※与えられるData Sourceの型によって型引数は変動することと，
　Streamの途中で値が加工されることで，型が変化していく場合もある．

③filterメソッドで「文字数が5より大きい値だけを取り出す」
　mapメソッドで「大文字に変換する」
　中間処理は複数あってもよし．省略してもよし．

④forEachメソッドで，得られた値をSystem.out::printlnメソッドで出力
　**終端処理は省略できません．**
　
中間処理の戻り値はいずれもStream\<T>です．
Stream APIでは，Streamの生成から中間処理/終端処理までを「.」演算子で
ひとまとめに連結でき，スマートな記述ができます．
(メソッドの連鎖という意味で**メソッドチェーン**と呼ばれる)

Streamの一連の処理が実行されるのは，終端処理のタイミングです．
中間で抽出/加工などの演算が呼び出されていても，それは一旦ストックされて，
その場では実行されず，終端処理まで処理の実施を待ちます．これを**遅延処理**といいます．

#Stream の作り方
##Collection/配列から生成
**Collectionから** 
Collection.stream()
**配列から** 
Arrays.stream(T[])
**Mapから**
Map.entrySet().stream()


stream()メソッドの並列版として，parallelStream()メソッドもあります．
streamをparallelStreamに置換するだけで，並列処理が可能になります．(強い…)
扱う要素数が多い場合，並列処理を有効にすることで，効率的に処理できる場合があります．
(勿論並列化のオーバーヘッドがあるため，必ずしも高速にはなりません．)
既存のStreamを並列化，あるいは直列化することもできます．

##StreamクラスからStream生成
Streamクラスでは，Stream生成するためのFactory Methodがあります．
最も基本的なのは，指定された可変数引数をStreamに変換するofメソッドです．

```java
var stream = Stream.of("tokyo","osaka","nagoya");
stream.forEach(System.out::println); // tokyo, osaka, nagoya
```
他にもgenerate()，builder()，concat()，iterator()があります．ここでは割愛します．

##プリミティブ型のStream生成
**IntStream**
intで特殊化されたストリーム
**LongStream**
longで特殊化されたストリーム
**DoubleStream**
doubleで特殊化されたストリーム

IntStream.range(int start, int endExclusive) [第2引数は範囲外：開空間]
IntStream.rangeClosed(int start,int endInclusive) [第2引数は範囲内：閉空間]

IntStreamを使った繰り返し処理の例は以下のようになります．
for文を使った場合と比較します．ちょっとオサレですね．

```java:for文を使った繰り返し
for(int i = 1; i <= 5 ; i++){
   System.out.println(i);
}
```

```java:IntStreamを使った繰り返し
IntStream.rangeClosed(1, 5).forEach(System.out::println);
```

Javaのジェネリクスの型引数では，プリミティブ型は使えないため，
Stream\<int>のような書き方はエラーになります．

##中間処理
Streamに流れる値を抽出/加工する役割を持ちます．
中間処理が実行されるのは，あくまで終端処理が呼び出されたタイミングであり，
呼び出しのたびに実行されるわけではないです．
###filter
指定された条件で値を抽出します．

```java
Stream.of("tokyo", "nagoya", "osaka").filter(s -> s.startsWith("t")).forEach(System.out::println); //tokyo
```
###map
与えられた値を加工します．

```java
Stream.of("tokyo", "nagoya", "osaka").map(s -> s.length)).forEach(System.out::println); //5, 6, 5
```
生成直後はStream\<String>だったのが，mapメソッドの後はStream\<Integer>になってることに注意．

###sorted
要素を並べ替えます．

```java
Stream.of("tokyo", "nagoya", "osaka").sorted().forEach(System.out::println); // nagoya, osaka, tokyo
Stream.of(2,1,3).sorted().forEach(System.out::println); // 1, 2, 3
```
デフォルトの動作は自然順序によるソートです．文字列ではれば辞書順，数値であれば大小でのソートです．
独自のソート規則を指定したい場合は，ソート規則をラムダ式で設定しましょう．
sorted()の引数はComparatorインターフェイスです．

```java
Stream.of("tokyo", "nagoya", "osaka").
  sorted((str1, str2) -> str1.length() - str2.length()).forEach(System.out::println); // tokyo, osaka, nagoya
```

###skip/limit
skip: m番目までの要素を切り捨てる
limit: n+1番目以降の要素を切り捨てる

```java
IntStream.range(1, 10).skip(3).limit(5).forEach(System.out::println); // 4, 5, 6, 7, 8
```
skipメソッドで最初の4要素をスキップし，
limitメソッドでそこから5個分の要素を取り出しています．
limitメソッドではすでに先頭が切り捨てたStreamを操作するため，引数に注意しましょう．

###peek
Streamの途中状態を確認します．
peekメソッドそのものはStreamに影響を与えないため，主にデバッグ用に使われます．

```java
Stream.of("tokyo", "nagoya", "osaka").peek(System.out::println).sorted().forEach(System.out::println);
//ソート前の結果：tokyo, nagoya, osaka ← peekのprintln
//ソート後の結果：nagoya, osaka, tokyo ← forEachのprintln
```

###distinct
値の重複を除去します．

```java
Stream.of("tokyo", "nagoya", "osaka", "osaka", "nagoya", "tokyo").distinct().forEach(System.out::println);
// tokyo, nagoya, osaka
```

##終端処理
Streamに流れる値を最終的に出力/集計する役割を持ちます．
Streamは終端処理の呼び出しをトリガーにして最終的にまとめて処理されるため，
中間処理と異なり，終端処理は省略できません．

終端処理したStreamを再利用することはできないため，
再度Stream処理を行いたい場合は，StreamそのものをData Sourceから再生成する必要があります．

###forEach
個々の要素を順に処理します．

```java
Stream.of("tokyo", "nagoya", "osaka").forEach(v -> System.out.println(v)); // tokyo, nagoya, osaka
Stream.of("tokyo", "nagoya", "osaka").forEach(System.out::println); // tokyo, nagoya, osaka
```

###findFirst
最初の値を取得します．

```java
System.out.println(Stream.of("tokyo", "nagoya", "osaka").filter(s -> s.startsWith("t")).findFirst().orElse("empty"));
// tokyo
```
空Streamの場合があるため，findFirstメソッドの戻り値はOptional型です．

```java:空の場合
System.out.println(Stream.of("tokyo", "nagoya", "osaka").filter(s -> s.startsWith("a")).findFirst().orElse("empty"));
// empty
```

###anyMatch/allMatch/noneMatch
値が特定の条件を満たすか判定します．
順に「条件式がtrueになる要素が存在するか」，「条件式がすべてtrueになるか」，
「条件式がすべてtrueにならないか」になります．

```java
System.out.println(Stream.of("tokyo", "nagoya", "osaka").anyMatch(v -> v.length() == 5)); // true
System.out.println(Stream.of("tokyo", "nagoya", "osaka").allMatch(v -> v.length() == 5)); // false
System.out.println(Stream.of("tokyo", "nagoya", "osaka").noneMatch(v -> v.length() == 5)); // false
```

###toArray
Stream処理の結果を文字列配列として変換します．

```java
var list = Stream.of("tokyo", "nagoya", "osaka").filter(s -> s.startsWith("t")).toArray();
```

###collect(コレクション変換)
Stream処理の結果をCollectionとして変換します．
collectメソッドにはCollectorsクラスで提供されている変換メソッドを渡します．
Listへの変換はtoList，Setへの変換はtoSet，マップへの変換はtoMapを使います．

```java
var list = Stream.of("tokyo", "nagoya", "osaka").filter(s -> s.startsWith("t")).collect(Collectors.toList());
```

collectメソッドはコレクション変換専用のメソッドというより，
リダクション処理を行うメソッドでもあります．リダクション処理の場合は後述します．

###min/max
最小値/最大値をもとめます．引数には比較規則(Comparator)を指定する必要があります．
戻り値がOptional型であるため，orElse経由になります．(ここではないことを意味する-1としています．)

```java
System.out.println(Stream.of(1, 3, 2).min((int1, int2) -> int1 - int2).orElse(-1)); // 1
System.out.println(Stream.of(1, 3, 2).min((int1, int2) -> int2 - int1).orElse(-1)); // 3
System.out.println(Stream.of(8, 7, 9).max((int1, int2) -> int1 - int2).orElse(-1)); // 9
System.out.println(Stream.of(8, 7, 9).max((int1, int2) -> int2 - int1).orElse(-1)); // 7
```

###count
要素の個数を求めます．

```java
System.out.println(Stream.of("tokyo", "nagoya", "osaka").filter(s -> s.length() > 5).count()); // 1
```

###reduce
Streamの値を一つにまとめます(Reduction)．
reduceメソッドは3種類のオーバーロードを提供しています．

####引数1個の場合
Optional reduce(BinaryOperator<T> accumulator);
戻り値がOptional型であるため，orElse経由になります．

引数は演算結果を格納するための変数result，個々の要素を受け取るための変数strがあります．

```java:引数1個の場合
System.out.println(
    Stream.of("tokyo", "nagoya", "osaka").sorted()
        .reduce((result, str) -> { return result + "," + str;}).orElse("")); // nagoya,osaka,tokyo
```

####引数2個の場合
T reduce(T identity, BinaryOperator<T> accumulator);
第一引数で初期値を受け取ることができます．
結果は非nullであることが明らかなため，非Optional型になります．OrElse経由は不要です．

```java:引数2個の場合
System.out.println(
    Stream.of("tokyo", "nagoya", "osaka").sorted()
        .reduce("hokkaido",  (result, str) -> { return result + "," + str;})); //hokkaido,nagoya,osaka,tokyo
```

####引数3個の場合
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
少々難しいかもしれません．Streamの要素型と，最終的な要素型が異なる場合に使います．ここでは例を割愛します．
詳細を知りたい方は[こちら](https://qiita.com/megmogmog1965/items/c9cf8639fc40c63b0e0a)の記事を見るといいかもしれません．

###collect(リダクション操作)
Stream内の要素をCollectionなどにまとめます．
reduceがStream内の要素をint，Stringのような単一値にリダクション処理するのに対し，
collectはCollection/StringBuilderのような可変な入れ物に対して値を蓄積してから返します(可変リダクション)．

こちらは少々難解なため，後程更新します…．

#終わりに
自分は3月までにJava Gold SE 11を取得するつもりです．
(そもそも前提としてJava Silver SE 11を受けないといけませんが…．)
Goldではラムダ式/Stream APIの出題率がとても高いため，整理しました．

Java SE 11 は半年前(2019/06下旬)くらいにできたばかりの資格で，
今まではSE 8 が最新版でした．Silver SE 11の参考書はすでに出ていますが，
Gold SE 11は現時点(2019/12)まだ参考書は出てないです．
SE 11 の試験ではSE 8の試験と違いラムダ式/Stream APIの使い方は
SE 11ベースになると思いますので，受験する方はご注意を．