Java Stream API　メソッド紹介 (Stream インタフェース)

# Java Stream API とは

Java SE8 から追加されたイテレーションの拡張 API です。

基本的な操作の流れは

1. stream を生成
1. 「中間操作」を任意の回数実施してコレクションの中身を操作
1. 「終端操作」を実施してコレクションに操作を適用したり、中間操作で変更されたコレクションを取得

例) 整数のリストから偶数のみを大きい順に取り出す

```java
List<Integer> list = Arrays.asList(5, 2, 8, 9, 3, 4);
List<Integer> evenList = list.stream()
        .filter(e -> (e % 2) == 0) // (中間操作) 偶数だけの stream を取得
        .sorted(reverseOrder()) // (中間操作) 降順にソート
        .collect(Collectors.toList()); // (終端操作) リストとして取得
```

stream を使い回すことは出来ないので注意。

```java
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream = list.stream().filter(e -> (e % 2) == 0); // 偶数だけの stream を取得
int max = stream.max(naturalOrder()).orElse(0); // list 内の偶数の最大値
int min = stream.min(naturalOrder()).orElse(0); // stream の再利用は禁止されており IllegalStateException 発生


// 下記のような実装は可能
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenList = list.stream()
        .filter(e -> (e % 2) == 0) // 偶数だけの stream を取得
        .collect(Collectors.toList()); // いったんリストを生成
int max = evenList.stream().max(naturalOrder()).orElse(0);
int min = evenList.stream().min(naturalOrder()).orElse(0);
```

# Stream の各種操作

## ストリームの生成

### コレクションをもとに stream を生成

`Stream<E> stream()`

※ Collection のメソッド
stream を利用する際はこれが一番利用されていると思います。
冒頭の例でも使用している通り、`list.stream()` のように使用します。

### stream を繋げる

`static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)`

2つの stream を繋げる。
3つ以上繋げたい場合は of().flatMap() を利用する。

```java
Stream s = Stream.concat(s1, s2);
Stream s = Stream.of(s1, s2, s3).flatMap(e -> e);
```

### builder から生成

```java
Stream s = Stream.builder().add(1).add(2).add(3).build();
```

### 空の stream

`static <T> Stream<T> empty()`

```java
Stream s = Stream.empty();
```

### 指定要素の順序付けされた stream

`static <T> Stream<T> of(T t)`
`@SafeVarargs static <T> Stream<T> of(T... values)`

```java
Stream s1 = Stream.of("a");
Stream s2 = Stream.of("a", "b", "c");
```

### 単一要素の stream (要素が null ならば空の stream) (Java 9 以降)

`static <T> Stream<T> ofNullable(T t)`

```java
Stream emptyStream = Stream.ofNullable(null);
Stream oneEleStream = Stream.ofNullable(1);
```

### 順序付けの無い無限順次ストリームを生成

`static <T> Stream<T> generate(Supplier<? extends T> s)`

指定された Supplier によって生成される要素を含む、順序付けされていない無限順次ストリームを生成する。

```java
Stream s = Stream.generate(random::nextInt); // 乱数の無限順次ストリーム
```

### 初期要素に繰り返し関数を適用することで生成される順序付け無限順次ストリームを生成 (Java 9 以降)

`static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)`

一つ目の要素は seed、二つ目以降は関数 f が繰り返し適用された無限順次ストリームを生成する。
要素は seed, f(seed), f(f(seed)), ... といった形になる。

```java
// "a", "aa", "aaa", ... といった無限順次ストリーム
Stream s = Stream.iterate("a", e -> e + "a");
```

### 終了条件のある iterate (Java 9 以降)

`static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)`

基本は `iterate(T seed, UnaryOperator<T> f)` と同じ。
hasNext が false を返すと要素の生成を停止する (hasNext が false となる要素は stream に含まれない)。

```java
Stream s = Stream.iterate("a", e -> e.length() < 5, e -> e + "a");
```

## 中間操作

### フィルタ系

#### 重複を削除

`Stream<T> distinct()`

```java
list = list.stream().distinct().toList(); // list から重複を除く
```

#### 先頭から n 個の要素を削除

`Stream<T> skip(long n)`

```java
Stream s = list.stream().skip(3); // 先頭から 3 つの要素を除く
```

#### 条件を指定してフィルタリング

`Stream<T> filter(Predicate<? super T> predicate)`

条件に**一致する要素**のみを返す。

```java
list = list.stream().filter(e -> e < 10).toList(); // 10 未満の要素のみにする
```

#### 条件に一致する間要素を取得 (Java 9 以降)

`default Stream<T> takeWhile(Predicate<? super T> predicate)`

先頭要素から見て行き、要素が predicate を満たす間その要素を取得する。
predicate が false になる要素が見つかった時点でその要素以降は削除される。
HashSet など順不同の場合は動作が非決定的になる。

```java
list = list.stream().takeWhile(e -> e < 10).toList();
// list の元の中身が [1, 9, 10] -> [1, 9]
//                 [9, 10, 1] -> [9]
//                 [10, 9, 1] -> []
```

#### 条件に一致しなくなるまで要素を削除 (Java 9 以降)

`Stream<T> dropWhile(Predicate<? super T> predicate)`

先頭要素から見て行き、要素が predicate を満たす間その要素を削除する。
predicate が false になる要素が見つかった時点で削除を止め、残った要素を返す。
HashSet など順不同の場合は動作が非決定的になる。

```java
list = list.stream().dropWhile(e -> e.startsWith("f")).toList();
// list の元の中身が ["foo", "fuga", "hoge"] -> ["hoge"]
//                 ["foo", "hoge", "fuga"] -> ["hoge", "fuga"]
//                 ["hoge", "foo", "fuga"] -> ["hoge", "foo", "fuga"]
```

#### 指定の要素数に切り詰める

`Stream<T> limit(long maxSize)`

先頭から maxSize 以内の長さになるように要素を切り詰める。

```java
Stream s = Stream.generate(() -> "a").limit(3);
```

### ソート系

#### 自然順序に従ってソート

`Stream<T> sorted()`

```java
var list = Stream.of(5, 3, 1, 2, 4)
		.sorted() // [1, 2, 3, 4, 5]
		.toList();
```

#### 指定の順序でソート

`Stream<T> sorted(Comparator<? super T> comparator)`

指定された comparator に従ってソート。

```java
itemList = itemList.stream()
		.sorted(Comparator.comparing(Item::id).reversed()) // ID の降順
		.toList();
```

### Stream の置き換え系

#### 指定した関数を適用した結果から構成される stream に置き換える

`<R> Stream<R> map(Function<? super T,? extends R> mapper)`

各要素に mapper を適用して得た stream を返す。

```java
public record Person(String name, List<String> hobby){}

List<Person> list = Arrays.asList(
		new Person("山田", Arrays.asList("けん玉")),
		new Person("鈴木", Arrays.asList("散歩", "映画")));
Stream s = list.stream().map(Person::hobby); // [ ["けん玉"], ["散歩", "映画"] ]
```

#### map の int 版

`IntStream mapToInt(ToIntFunction<? super T> mapper)`

```java
int sum = list.stream()
		.mapToInt(Integer::parseInt)
		.sum(); // 6
```

#### map の long 版

`LongStream mapToLong(ToLongFunction<? super T> mapper)`

```java
List<String> list = Arrays.asList("1000000000", "2000000000", "3000000000");
long sum = list.stream()
		.mapToLong(Long::parseLong)
		.sum(); // 6000000000
```

#### map の double 版

`DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)`

```java
List<String> list = Arrays.asList("0.1", "2.3", "4");
Double sum = list.stream()
		.mapToDouble(Double::parseDouble)
		.sum(); // 6.4
```

#### 指定したマッピング関数で得られる stream に置き換える

`<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)`

①各要素に mapper を適用して stream を得て、②それらをひとまとめにした stream を返す。
`map(Function<? super T,? extends R> mapper)` との違いはフラットな stream として返すかどうか。
→ ①の時点で結果を返すのが map、①の結果をフラットな状態にして返すのが flatMap

イメージ：
　[Aさんのペットリスト, Bさんのペットリスト]
→ [ [猫A, 猫B], [猫C, 猫D] ] ① 各ペットリストから猫リストの stream を得る
→ [猫A, 猫B, 猫C, 猫D] ② 得た結果をフラットな stream として返す

```java
public record Pet(List<Dog> dogList, List<Cat> catList){}
public record Dog(String name){}
public record Cat(String name){}

List<Cat> catList = petList.stream()
        .flatMap(pet -> pet.catList().stream()) // Stream<Cat>
        // .map(pet -> pet.catList().stream()) の場合は Stream<Stream<Cat>> が返る
        .toList();
```

#### flatMap の int 版 (IntStream への置き換え)

`IntStream flatMapToInt(Function<? super T,? extends IntStream> mapper)`

```java
int[][] intArray = {{1, 2}, {3, 4, 5}};
int sum = Arrays.stream(intArray)
		.flatMapToInt(Arrays::stream)
		.sum(); // 15
```

#### flatMap の Long 版 (LongStream への置き換え)

`LongStream flatMapToLong(Function<? super T,? extends LongStream> mapper)`

```java
long[][] intArray = {{1L, 2L}, {3L, 4L, 5L}};
long sum = Arrays.stream(intArray)
		.flatMapToLong(Arrays::stream)
		.sum(); // 15L
```

#### flatMap の Double 版 (DoubleStream への置き換え)

`DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper)`

```java
double[][] intArray = {{0.1, 0.2}, {1.3, 2.1, 5}};
double sum = Arrays.stream(intArray)
		.flatMapToDouble(Arrays::stream)
		.sum(); // 8.7
```

#### 各要素を複数の要素で置き換えた結果で構成されるストリームへ置き換え (Java 16 以降)

`default <R> Stream<R> mapMulti(BiConsumer<? super T,? super Consumer<R>> mapper)`

置換要素を受け入れる consumer とともに、指定されたマッピング関数を各要素に適用する。

```java
private record Person(String name, List<Hobby> hobbies){}
private record Hobby(List<String> outdoors, List<String> indoors){}

List<Person> list = List.of(
        new Person("山田太郎", List.of(new Hobby(List.of("キャンプ", "釣り"), List.of("マンガ")))),
        new Person("鈴木一郎", List.of(new Hobby(List.of("釣り"), List.of("読書", "映画鑑賞"))))
);
// 全ての趣味を表示する (重複は削除)
list.stream()
        .flatMap(person -> person.hobbies().stream())
        .mapMulti((hobby, consumer) -> {
            hobby.outdoors().forEach(consumer);
            hobby.indoors().forEach(consumer);
        })
        .distinct()
        .forEach(e -> System.out.print(e + "  ")); // キャンプ  釣り  マンガ  読書  映画鑑賞
```

#### mapMulti の IntConsumer 版 (Java 16 以降)

`default IntStream mapMultiToInt(BiConsumer<? super T,? super IntConsumer> mapper)`

```java
IntStream is = list.stream()
		.mapMultiToInt((e, consumer) -> consumer.accept(e));
```

#### mapMulti の LongConsumer 版 (Java 16 以降)

`default LongStream mapMultiToLong(BiConsumer<? super T,? super LongConsumer> mapper)`

```java
LongStream ls = list.stream()
		.mapMultiToLong((e, consumer) -> consumer.accept(e));
```

#### mapMulti の DoubleConsumer 版 (Java 16 以降)

`default DoubleStream mapMultiToDouble(BiConsumer<? super T,? super DoubleConsumer> mapper)`

```java
DoubleStream ls = list.stream()
		.mapMultiToDouble((e, consumer) -> consumer.accept(e));
```

#### stream をそのまま返し、消費される各要素に指定されたアクションを実行

`Stream<T> peek(Consumer<? super T> action)`

stream の要素からなる stream を返す。
元 stream から消費される各要素に対して指定された非干渉アクションを実行する。
要素がパイプライン内の特定ポイントを通過する際に、その内容を確認するようなデバッグのサポートが目的。

```java
list = list.stream()
		.filter(e -> e instanceof Integer)
		// int だけになっていることをプリントデバッグで確認
		.peek(e -> System.out.println("Filtered value: " + e))
		.collect(Collectors.toList());
```

## 終端操作

### 評価系

#### 全ての要素が条件を満たすか？

`boolean allMatch(Predicate<? super T> predicate)`

ストリームの全要素が predicate に一致するなら true, 一致しない要素があるならば false を返す。
ストリームが空の場合は true を返す。

```java
// 全て偶数: true, 奇数が含まれる: false
list.stream().allMatch(e -> e % 2 == 0);
```

#### 全ての要素が条件を満たさないか？

`boolean noneMatch(Predicate<? super T> predicate)`

predicate に一致する要素が存在しなければ true, ひとつでも一致する要素があるならば false を返す。
ストリームが空の場合は **true** を返す。

```java
// 偶数を含まない: true, 偶数が含まれる: false
list.stream().noneMatch(e -> e % 2 == 0);
```

#### 条件を満たす要素がひとつでも存在するか？

`boolean anyMatch(Predicate<? super T> predicate)`

ストリームのいずれかの要素が predicate に一致するなら true, 一致しないなら false を返す。
ストリームが空の場合は **false** を返す。

```java
// 文字列 "err" という要素が存在する: true, 存在しない: false
list.stream().anyMatch(Predicate.isEqual("err"));
```

### 要素の取得系

#### 配列にする

`Object[] toArray()`
`<A> A[] toArray(IntFunction<A[]> generator)`

generator を指定しなければ Object の配列になります。

```java
Object[] oArray = Stream.of(1, 2, 3).toArray();
Integer[] iArray = Stream.of(1, 2, 3).toArray(Integer[]::new);
```

#### List にする (Java 16 以降)

`default List<T> toList()`

stream が順序付けされている場合はその順序になる。

```java
List<Integer> list = Stream.of(1, 2, 3).toList();
```

#### 指定のコレクションに変換する

`<R, A> R collect(Collector<? super T, A, R> collector)`

steam の要素に対する [可変リダクション操作](https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/package-summary.html#MutableReduction) を行う。
各種リダクション操作は[こちら](https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Collectors.html)を参照。
主観で一番利用頻度の高いものは `Collectors.toList()` でしたが、Java 16 から `Stream#toList()` が使用できるので利用頻度は落ちそう。
`Collectors.groupingBy()` で Map にするために利用することがメインになるかも？

```java
// 中間操作を適用した後、リストに詰め直す
list = list.stream()./* 中間操作 */.collect(Collectors.toList());
// city を key にして Map に分類
Map<String, List<Person>> peopleByCity = personList.stream()
        .collect(Collectors.groupingBy(Person::city));
```

#### いずれか一つの要素を取得

`Optional<T> findAny()`

要素のうちいずれか一つを Optional で返す。
stream が空の場合は空の Optional を返す。
非決定的であり、同じソースに対して呼び出すたびに異なる結果が返る可能性がある。

```java
Optional o = list.stream().findAny();
```

#### 先頭の要素を取得

`Optional<T> findFirst()`

最初の要素を Optional で返す。
stream が空の場合は空の Optional を返す。

```java
Optional o = list.stream().findFirst();
```

#### 最大値を取得

`Optional<T> max(Comparator<? super T> comparator)`

指定された Comparator に従い、最大値を Optional で返す。

```java
Optional<Integer> max = list.stream().max(Comparator.naturalOrder());
```

#### 最小値を取得

`Optional<T> min(Comparator<? super T> comparator)`

指定された Comparator に従い、最小値を Optional で返す。

```java
Optional<Integer> min = list.stream().min(Comparator.naturalOrder());
```

### その他処理の適用系

#### 要素数を数える

`long count()`

```java
long cnt = list.stream().count(); // list の要素数
```

#### 処理を適用する

`void forEach(Consumer<? super T> action)`

stream の各要素に対して action を実行する。
検出順序は保証されない。

```java
list.stream().forEach(System.out::println);
```

#### 処理をストリームの検出順に適用する

`void forEachOrdered(Consumer<? super T> action)`

stream の検出順序が定義されている場合はその順序で action が適用される forEach()。

```java
list.stream().forEachOrdered(System.out::println);
```

#### 結合的累積関数を使用してリダクションされた Optional を返す

`Optional<T> reduce(BinaryOperator<T> accumulator)`

accumulator を適用した結果を Optional で返す。

```java
// 文字列結合
String s = Stream.of("a", "b", "c")
		.reduce((result, e) -> result + e)
		.orElse("");
// Set の和集合を求める
List<Set<Integer>> list = Arrays.asList(set1, set2, ...);
Set<Integer> unionSet = list.stream()
		.reduce((set, value) -> {
			Set<Integer> tmp = new HashSet<>(set);
			tmp.addAll(value);
			return tmp;
		})
		.orElse(new HashSet<>());
```

#### reduce の初期値あり版

`T reduce(T identity, BinaryOperator<T> accumulator)`

```java
String s = Stream.of("a", "b", "c")
		.reduce("String: ", (acc, e) -> acc + e); // "String: abc"
```

#### reduce で変換機能を兼ねた累積関数を利用する

`<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)`

初期値 identity として累積関数 accumulator を適用する。
accumulator では `T` から `U` への変換機能を有する。
**並列 stream の場合は** accumulator の適用結果が複数生まれるので、combiner を用いて結合する (`list.stream()` の場合は combiner は無視される)。

```java
List<String> animals = Arrays.asList("dog", "cat", ...);
long dogCount = animals.parallelStream()
		.reduce(0L, (count, animal) -> "dog".equals(animal) ? count + 1 : count, Long::sum);
```

上記コードの補足説明
- **identity**: 初期値は 0 とする
- **accumulator**: 要素が "dog" であればその数を数える (String から long への変換機能 & 累積機能)
- **combiner**: accumulator で得たカウント数を合計する
    - 並列 stream の場合は accumulator の結果が複数生まれるため

# 参考資料
https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html