StreamAPIの概要

# はじめに
業務でよく使用しているStreamAPIについてまとめました。
煩雑な処理をわかりやすいコードで短く記述することができる便利な拡張APIです。
Java8以降から使用できるため興味のある方は是非試してみてください。

# StreamAPIとは
StreamAPIとは、Java8で導入された機能で、配列やCollectionなどの要素の集合に対して行う
煩雑な処理をわかりやすいコードで短く記述することができるイテレーションの拡張APIです。

# サンプルコード
説明に使用するサンプルコードを用意します。
StreamAPIには、直接的な関係はありません。
### `Tweet`クラス
```java:Tweet.java
public class Tweet {

  /** 投稿者 */
  private String contributor;

  /** 投稿日 */
  private LocalDate date;

  /** コンストラクタ */
  public Tweet(String contributor, LocalDate date) {
    this.contributor = contributor;
    this.date = date;
  }

  public String getContributor() {
    return this.contributor;
  }

  public void setContributor(String contributor) {
    this.contributor = contributor;
  }

  public LocalDate getDate() {
    return this.date;
  }

  public void setDate(LocalDate date) {
    this.date = date;
  }
}
```
### `Sample`クラス
```java:Sample.java
public class Sample {
  public static void main(String[] args) {
    // サンプルデータの生成
    Tweet taro1 = new Tweet("Taro", LocalDate.of(2022, 7, 1));
    Tweet taro2 = new Tweet("Taro", LocalDate.of(2019, 9, 1));
    Tweet taro3 = new Tweet("Taro", LocalDate.of(2019, 8, 31));

    Tweet hanako1 = new Tweet("Hanako", LocalDate.of(2021, 12, 5));
    Tweet hanako2 = new Tweet("Hanako", LocalDate.of(2020, 10, 1));
    Tweet hanako3 = new Tweet("Hanako", LocalDate.of(2016, 6, 20));

    Tweet saburo1 = new Tweet("Saburo", LocalDate.of(2017, 1, 16));
    Tweet saburo2 = new Tweet("Saburo", LocalDate.of(2015, 7, 25));
    Tweet saburo3 = new Tweet("Saburo", LocalDate.of(2022, 2, 13));

    // リスト作成
    List<Tweet> lists = Arrays.asList(taro1, taro2, taro3, hanako1, hanako2, hanako3, saburo1, saburo2, saburo3);
  }
}
```

# 基本的な処理の流れ
StreamAPIの処理の流れは以下の3段階に分けることができます。
1. 配列やCollectionからstreamを生成する。
2. 生成したstreamに対して「中間操作」を行う。処理しやすいようにstreamの中身を変換する。
3. 変換したstreamの中身に対して「終端操作」を行う。処理を適用する。

一つずつ解説していきます。

# streamの生成
streamの生成方法を紹介します。streamは、ListやMap、配列によって生成方法が異なります。
ここでは、以下の3種類を紹介します。

### 配列からstreamを生成する場合
配列からstreamを生成する場合、`Arrays.stream()`メソッドを使用します。
```java
String[] sports = {"野球", "サッカー", "バスケット", "バレー", "テニス", "水泳", "卓球"};
Stream<String> stream = Arrays.stream(sports);
```
### ListやSetからstreamを生成する場合
ListやSetからstreamを生成する場合、`stream()`メソッドを使用します。
```java
List<String> sports = List.of("野球", "サッカー", "バスケット", "バレー", "テニス", "水泳", "卓球");
Stream<String> stream = sports.stream();
```

### Mapからstreamを生成する場合
Mapから直接Streamを生成するメソッドが存在しないため、
`entrySet（）`メソッドを使用し、Set型に変換したあとstreamを生成します。
```java
Map<String, Integer> sample = Map.of("key1", 100, "key2", 200, "key3", 300, "key4", 400);
Stream<Entry<String, Integer>> stream = sample.entrySet().stream();
```
# 中間操作
次に、「中間操作」についてです。
中間操作では、streamの中身を扱いたいように変換する処理を行います。
中間操作を行なっても元の要素(配列やCollection)には影響を与えません。

ここでは、サンプルコードを用いて、よく使用する以下の3種類のメソッドを紹介します。
他にも中間操作のメソッドは複数あるため必要に応じて適宜調べてみてください。
### `filter`メソッド
引数で指定した条件に一致する要素を抽出するメソッドです。
今回は、投稿者を "Taro" で絞っています。
```java:Sample.java
public class Sample {
  public static void main(String[] args) {
    <!-- 中略 -->
    lists.stream().filter(list -> list.getContributor().equals("Taro"))
    			  .forEach(list -> System.out.println(list.getContributor() + " " + list.getDate()));

    System.out.println("===========================");
    // filterメソッドを使用しても、List自体には影響のないことを確認
    lists.stream().forEach(list -> System.out.println(list.getContributor() + " " + list.getDate()));    
  }
}
```

```:実行結果
Taro 2022-07-01
Taro 2019-09-01
Taro 2019-08-31
===========================
Taro 2022-07-01
Taro 2019-09-01
Taro 2019-08-31
Hanako 2021-12-05
Hanako 2020-10-01
Hanako 2016-06-20
Saburo 2017-01-16
Saburo 2015-07-25
Saburo 2022-02-13
```
### `map`メソッド
要素の値や型を別のものに変換するメソッドです。
今回は、投稿者名を大文字に変換しています。
```java:Sample.java
public class Sample {
  public static void main(String[] args) {
    <!-- 中略 -->
    lists.stream().map(list -> list.getContributor().toUpperCase())
                  .forEach(list -> System.out.println(list));    
  }
}
```

```:実行結果
TARO
TARO
TARO
HANAKO
HANAKO
HANAKO
SABURO
SABURO
SABURO
```
### `sorted`メソッド
要素を並び替えるメソッドです。
今回は、投稿者順に昇順で並び替えています。
```java:Sample.java
public class Sample {
  public static void main(String[] args) {
    <!-- 中略 -->
    lists.stream().sorted(Comparator.comparing(Tweet::getContributor))
                  .forEach(list -> System.out.println(list.getContributor() + " " + list.getDate()));   
  }
}
```

```:実行結果
Hanako 2021-12-05
Hanako 2020-10-01
Hanako 2016-06-20
Saburo 2017-01-16
Saburo 2015-07-25
Saburo 2022-02-13
Taro 2022-07-01
Taro 2019-09-01
Taro 2019-08-31
```
#### 複数条件でソートする
複数条件で並び替えを行いたい場合、以下のようになります。
投稿者順に昇順で並び替えたあと、さらに投稿日順に降順で並び替えています。
```java:Sample.java
public class Sample {
  public static void main(String[] args) {
    <!-- 中略 -->  
    lists.stream().sorted(Comparator.comparing(Tweet::getContributor).thenComparing(Tweet::getDate, Comparator.reverseOrder()))
                  .forEach(list -> System.out.println(list.getContributor() + " " + list.getDate()));  
  }
}
```

```:実行結果
Hanako 2021-12-05
Hanako 2020-10-01
Hanako 2016-06-20
Saburo 2022-02-13
Saburo 2017-01-16
Saburo 2015-07-25
Taro 2022-07-01
Taro 2019-09-01
Taro 2019-08-31
```
# 終端操作
最後に、「終端操作」についてです。streamに対して、処理を適用して行きます。
ここでは、サンプルコードを用いて、よく使用するメソッドを1つ紹介します。
他にも終端操作のメソッドは複数あるため必要に応じて適宜調べてみてください。
### `collect`メソッド
中間操作で抽出した一連の要素に特定の処理を適用して1つにまとめるメソッドです。
ここでは、中間操作にて並び替えを行なった要素で新しいリストを作成します。
※ 中間操作は元のリスト自体には影響を与えないため、`collect()`を使用し新しいリストを作成する。
```java:Sample.java
public class Sample {
  public static void main(String[] args) {
    <!-- 中略 -->
    lists = lists.stream().sorted(Comparator.comparing(Tweet::getContributor))
                          .collect(Collectors.toList());
    
    // Listの中身がソートされていることを確認
    lists.stream().forEach(list -> System.out.println(list.getContributor() + " " + list.getDate()));  
  }
}
```

```:実行結果
Hanako 2021-12-05
Hanako 2020-10-01
Hanako 2016-06-20
Saburo 2017-01-16
Saburo 2015-07-25
Saburo 2022-02-13
Taro 2022-07-01
Taro 2019-09-01
Taro 2019-08-31
```
# おわりに
StreamAPIの説明は以上になります。
慣れるまで読みづらかったですが、私自身ようやく慣れてきて、便利に感じることができました。
間違っている箇所ありましたら、ご指摘いただければ幸いです。