JavaのStreamAPIについて

## はじめに
StreamAPIの入門者向け記事です。
StreamAPIで使われるラムダ式については解説していません。（[この記事](https://qiita.com/taumax/items/302b5025e30b7a74c8c6)を参考にしてください）


## StreamAPIとは
StreamAPIはJava SE8から追加されたコレクションの取り扱いをいい感じにできる機能です。
基本的には以下の流れで処理をします。
1. Streamを取得
1. 中間操作を実行してStreamの要素に対して操作を加える
1. 終端操作を実行して結果を取得

java.util.streamパッケージに関連するクラスがまとめられています。

## 使い方
### Streamの種類
主にStreamオブジェクトは以下のものがあります。
通常のStreamは参照型の値しか扱うことができないので、プリミティブ型を扱うStreamが別に用意されています。
| Stream | 扱う型 |
| ------- | ------- |
| Stream | 参照型 |
| IntStream | int型 |
| LongStream | long型 |
| DoubleStream | double型 |

### Streamの取得方法
以下のようなパターンでStreamが取得できます。

```Java
// ListからStreamを取得
List<String> list = List.of("twitter", "instagram", "facebook");
Stream<String> stream = list.stream();

// 配列からStreamを取得
String[] strArraly = {"twitter", "instagram", "facebook"};
Stream<String> stream = Stream.of(strArraly);

// プリミティブ型を扱うStreamを取得
IntStream stream = IntStream.of({1, 2, 3, 4, 5});

// Stream.of()だとInteger型を扱うStreamが取得される
Stream<Integer> stream = Stream.of({1, 2, 3, 4, 5});

// 要素数が無限
Stream<Integer> stream = Stream.generate(() -> new Random().nextInt());
```

### 中間操作
Stream内の要素を操作し、新しいStreamを返す操作です。
新しいStreamを返すことからメソッドチェーンでつないで記述することができます。

##### 中間操作一覧（一部）
| メソッド | 内容 | 引数 |
| ----- | ----- | ----- |
| filter | 条件に一致する要素のみのStreamを返す | Predicate |
| map | 別の型を扱うStreamを返す | Function |
| mapToInt | intStreamを返す | Function |
| distinct | 要素の重複を排除したStreamを返す<br>※equalsメソッドでtrueを返すもの | なし |
| limit | 指定された要素数のStreamを返す | int |
| skip | 指定された数分の要素を飛ばしたStreamを返す | int |
| sorted | 引数で指定した順序に従ってソートされたStreamを返す<br>※引数なしの場合は自然順序に従う | Comparator |
| peek | 現在の要素に対して関数を適用し、Streamを返す | Consumer |

使用例

```Java
// tを含む要素のみを扱うStreamに変換
Stream<String> stream = List.of("twitter", "instagram", "facebook").stream().filter(str -> str.contains("t")); 
// "twitter", "instagram" を扱うStream<String>を返す

// 文字列の文字数を扱うStreamに変換
Stream<Integer> stream = List.of("twitter", "instagram", "facebook").stream().map(str -> str.length());
// 7, 9, 8を扱うStream<Integer>を返す

// IntStream型のStreamに変換
IntStream stream = List.of("twitter", "instagram", "facebook").stream().mapToInt(str -> str.length());
// 7, 9, 8を扱うIntStreamを返す

// 重複を排除
Stream<String> stream = List.of("twitter", "instagram", "facebook", "twitter").stream().distinct();
// "twitter", "instagram", "facebook"を扱うStream<String>を返す

// 要素数を限定する
Stream<String> stream = Stream.generate(() -> new Random().nextInt()).limit(5);
// 5つの乱数を扱うStream<Integer>を返す

// 最初の要素を指定分スキップする
Stream<String> stream = List.of("twitter", "instagram", "facebook").stream().skip(1);
// "instagram", "facebook"を扱うStream<String>を返す

// 要素の順番を並び替える
Stream<String> stream = List.of("twitter", "instagram", "facebook").stream().sorted();
// "facebook", "instagram", "twitter"を扱うStream<String>を返す

// 要素の状態を表示する
Stream<String> stream = List.of("twitter", "instagram", "facebook").stream().peek(str -> System.out.print(str + " "));
// コンソールに twitter instagram facebookと表示し、"twitter", "instagram", "facebook"を扱うStream<String>を返す

// メソッドチェーン
List.of("twitter", "instagram", "facebook").stream().map(str -> str.length()).filter(len -> len > 7);
// 7, 9, 8を扱うStream<Integer>に変換し、9, 8を扱うStream<Integer>を返す
```

### 終端操作
Streamオブジェクトを最終的に処理する操作です。
一つのStreamオブジェクトに対して1回しか呼び出せません。

以下の例は実行時に例外が発生します。
```Java
Stream<String> stream = List.of("twitter", "instagram", "facebook").stream();
stream.forEach(str -> System.out.println(str));
stream.forEach(str -> System.out.println(str)); // IllegalStateExceptionがスローされる
```

##### 終端操作一覧
| メソッド | 内容 | 引数 | 戻り値 |
| - | - | - | - |
| forEach | Streamの要素一つ一つに処理を行う | Consumer | void |
| count | Streamの要素数を返す | なし | int |
| collect | Streamの要素を持つコレクションを返す | Collector<br>Supplier, BiConsumer, BiConsumer | Collectorで指定した型 |
| reduce | Streamの要素を結合して返す | BinaryOparator<br>T（初期値）, BinaryOperator | Optional\<T><br>T |

collectメソッドの引数、Collectorは奥が深いので[\[Java\]StreamのCollectorsのメモ](https://qiita.com/yoshi389111/items/1e316d7e98f02c20a8ca)を参照してください。

```Java
// stream要素
Stream<String> stream = List.of("twitter", "instagram", "facebook", "twitter").stream();

// 要素を1つずつコンソールに出力
stream.forEach(str -> System.out.println(str));

// 要素の個数を取得
long count = stream.count();

// 要素をリストにして取得（Collectorsを使用する）
List<String> list  = stream.map(str -> str.toUpperCase()).collect(Collectors.toList());

// 要素をリストにして取得(関数型インタフェースを使用する。↑と同じ)
List<String> list = stream.collect(() -> new ArrayList<>(), 
    (List<String> list, String str) -> list.add(str),
    (List<String> left, List<String> right) -> left.addAll(right)
);

// 要素の文字数を合計して取得
Integer length = stream.map(str -> str.length()).reduce(0, (a, b) -> a + b);

// 初期値を指定しなかった場合はOptionalが返される
Optional<Integer> length = stream.map(str -> str.length()).reduce((a, b) -> a + b);
```