【初投稿】Stream APIについて新卒がまとめてみた

## はじめに...
新卒1年目として、2024年4月からIT業界に飛び込んできました。
大学・大学院ともに数学を専攻していた私にとって、プログラミングは全く触れたことのない世界でした。
そんな私が初めてJavaで実装タスクを担当した際、便利だなと感じた **``Stream API``** についてまとめてみました。

## ○Stream API
### ・Stream APIとは

**``リストやセット(コレクション)に対して、行いたい操作を宣言的に書くことができるようにするAPI``**

宣言的に書けることで可読性も上がり、コードもシンプルになるというメリットがあります。
使用する際は次のような形で書きます。
```java:Stream APIの書き方
// 処理したいリストの作成(Integerの場合)
List<Integer> numbers = Arrays.asList(...);
// 処理の実行(各種操作は以下参照)
anyList.stream().(中間操作).(終端操作);
```
stream処理を行いたいコレクションに対して、以下の中間操作・終端操作を選択することで様々な処理を行うことができます。
([こちら](https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Stream.html)から抜粋しています。どういう仕組みで動いているのか知りたい方はぜひご覧ください。)

```:中間操作と終端操作
【中間操作】
・filter             - 指定された条件に一致するものを抜き出す
・map                - 指定された関数に一致するものを抜き出す
・distinct           - 重複を除く
・sorted             - 自然順序に従ってソートする

【終端操作】
・forEach            - 各要素に対してアクションを実行する
・toArray            - streamの要素を含む配列を返却する
・collect            - streamの要素を集約する
・min/max            - streamの最小/最大要素を返却する
・count              - streamの要素の個数を返却する
・anyMatch/allMatch  - いずれかの/すべての要素が指定された条件に一致するかどうかを返却する
・noneMatch          - すべての要素が指定された条件に一致しないかどうかを返却する
...etc
```

どれも ``for`` や ``if`` を使用して記述できる内容・処理ではありますが、``Stream API``を使用することで「宣言的」に記述可能で可読性、簡潔さが高くなります。

例えば...
「リストから偶数だけを抜き出し、2倍したものだけを表示する」
において、以下のように可読性、簡潔さに影響します。

```java:for, ifを使用
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> result = new ArrayList<>();

for (Integer number : numbers) {
    if (number % 2 == 0) {
        result.add(number * 2);
    }
}

System.out.println(result); // [4, 8, 12]
```

```java:Stream APIを使用
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

numbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .map(n -> n * 2)
                .collect(Collectors.toList());

System.out.println(numbers); // [4, 8, 12]
```
この程度の処理なら一見影響はないように見えますが、リストの次元が増えたり条件が増えたりするとその影響は明らかでしょう。
（逆にリストの結合のみなどの単純な処理においては、``Stream API``を使用するメリットはないと言えますね...）

### ・sorted()メソッド
私が実際に使用した ``sorted()``メソッドについてまとめました。

#### ○Comparater
``sorted()``を使用してコレクションをソートするとき、ソートの条件を指定するために``Comparator``を使用します。

例を用いて具体的に確認してみます。

**○例1. 数値を降順でソート**

降順で並べる条件を付与します。
　→ ``sorted(Comparator.reverseOrder())``
 
```java:例1
List<Integer> numbers = Arrays.asList(3, 5, 8, 7, 2);

numbers = numbers.stream()
                .sorted(Comparator.reverseOrder())
                .toList();

System.out.println(numbers) // [8, 7, 5, 3, 2]
```
**○例2. 3人を年齢昇順で並び替える**

Comparatorに年齢を昇順で並べるという条件を付与します。
　→ ``sorted(Comparator.comparingInt((Person person) -> person.age))``
 
```java:例2
class Person {
    string name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return name + "(" + age + ")";
    }
}

public class Example {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 20),
            new Person("Bob", 15),
            new Person("Charlie, 25")
        );

        List<Person> result = people.stream()
                                .sorted(Comparator.comparingInt((Person person) -> person.age))
                                .toList(); // バージョンが古い場合 .collect(Collectors.toList()) を使用

        System.out.println(result); // [Bob(15), Alice(20), Charlie(25)]
    }
}
```

**○例3. 出身地、年齢、名前の順番で並び替える**
``Comparator``に出身地 > 年齢 > 名前の順で並び替える条件を付与します。
　→``sorted(Comparator.comparing(Person::getHome)).thenComparingInt(Person::getAge).thenComparing(Person::getName))``

```java:例3
class Person {
    string name;
    int age;
    string home;

    Person(String name, int age, string home) {
        this.name = name;
        this.age = age;
        this.home = home;
    }

    public String toString() {
        return "[" + home + "]" + name + "(" + age + ")";
    }
}

public class Example {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 20 ,America),
            new Person("Akane", 20 ,Japan)
            new Person("Bob", 15, America),
            new Person("Aiko", 25, Japan)
            new Person("Chika", 20, Japan)
        );

        List<Person> result = people.stream()
                                .sorted(Comparator.comparing(Person::getHome))
                                .thenComparingInt(Person::getAge)
                                .thenComparing(Person::getName))
                                .toList(); // バージョンが古い場合 .collect(Collectors.toList()) を使用

        System.out.println(result); 
        // [[America]Alice(20), [America]Bob(15), [Japan]Akane(20), [Japan]Aiko(25), [Japan]Chika(20)]
    }
}
```

**○例4 並び替えたい順番を決め、その順番に並び替える**
並び替えたい順番(``order``)を定義し、条件に付与します。
　→ ``sorted(Comparator.comparing(order::indexOf))``

```java:例1
List<Integer> numbers = Arrays.asList(3, 5, 8, 7, 2);
List<Integer> order = Arrays.asList(1, 2, 5, 4, 3, 6, 7, 8);

numbers = numbers.stream()
                .sorted(Comparator.comparing(order::indexOf))
                .toList();

System.out.println(numbers) // [5, 2, 3, 7, 8]
```

## まとめ
実装で初めてStreamを目の当たりにしたとき、Listの操作を行っていることすら理解できていませんでしたが、調べて使っていくうちにStreamの便利さと奥深さをとても感じました。

今回詳しく紹介した``sorted()``メソッドはStream APIのほんの一部に過ぎませんが、1年目ながら比較的使用率が高いと感じたので、備忘録も兼ねてまとめてみました。

同じように初めてStreamを目にした人に届きますように...