JavaのStreamAPIを活用する

# はじめに

こんにちは、Gakken LEAP のエンジニアの okamoto です。
本記事ではJavaのStream APIを利用する際の記述方法やその活用方法についてまとめます。

# Streamとは

Stream APIはJava 8から導入されたAPIで、リストやコレクションなどのデータを一つずつ取り出して処理することができます。
利用することでリストを手動で処理させることなく、処理をシンプルにまとめることができます。

# 使用例

下記リスト内から「A」から始まる要素を取り出したい場合

```java
List<String> fruits = Arrays.asList("Apple", "Orange", "Peach", "Apricot");
```

## ・forループの場合

```java
List<String> result = new ArrayList<>();
for (String fruit : fruits) {
    if (fruit.startsWith("A")) {
        result.add(fruit);
    }
}
```

forループでは、リストを手動で処理し、条件に合った要素を新しいリストに追加する必要があり、手続き的な処理を意識して書かなければなりません。

## ・streamの場合

```java
List<String> startWithA = fruits.stream()
    .filter(fruit -> fruit.startsWith("A"))
    .collect(Collectors.toList());
```

Streamを使うと、要素のフィルタリングからリストへの変換までをシンプルにまとめることができます。
forループに比べてコードが短く、何をしたいのかが一目で分かります。

# Streamの特徴的な記述法

## ラムダ式

ラムダ式は、関数型インターフェースを簡潔に記述するための書き方です。例えば、filterメソッドの中で使われるnum -> num % 2 == 0は、数値が偶数かどうかを判定するラムダ式です。

```java
filter(num -> num % 2 == 0)
```

## メソッド参照 (::)

メソッド参照は、既存のメソッドをそのまま使いたいときに使用します。例えば、System.out::printlnという記述は、System.out.printlnメソッドをそのまま呼び出すことを意味します。

下記の例の場合、fruitsのリストの内容が順に出力されます。

```java
fruits.forEach(System.out::println);
```

# よく使われる機能

Streamでは、以下のような操作がよく使われます。

## collect

collectメソッドは、ストリームで処理を行った結果をリストやセットなどのコレクションにまとめるために使います。

## map

mapメソッドは、ストリーム内の要素を別の型や形に変換するために使用されます。

下記の例のように記述することで文字列のリストをその長さのリストに変換することができます。

```java
List<Integer> fruitLengths = fruits.stream()
    .map(fruit -> fruit.length())
    .collect(Collectors.toList());
System.out.println(fruitLengths);  // [5, 6, 5, 7]
```

## filter

filterメソッドは、ストリーム内の要素を条件に基づいて絞り込むために使用されます。

下記の例のように、フィルタリングした結果を新たなリストにまとめることができます。

```java
List<String> startWithA = fruits.stream()
    .filter(fruit -> fruit.startsWith("A"))
    .collect(Collectors.toList());
System.out.println(startWithA);  // [Apple, Apricot]
```

## sorted

sortedメソッドは、ストリーム内の要素を並び替える際に使用されます。

下記の例のように、リスト内の要素をアルファベット順に並び替えることができます。

```java
List<String> sortedByLength = fruits.stream()
    .sorted(Comparator.comparingInt(String::length))
    .collect(Collectors.toList());
System.out.println(sortedByLength);  // [Apple, Peach, Orange, Apricot]
```

## reduce

reduceは、複数の要素を1つの値にまとめるために使用されます。

下記の例のように、リスト内の要素を合計する場合などに使用します。

```java
int result = Arrays.asList(1, 2, 3, 4, 5)
              .stream()
              .reduce(0, (sum, num) -> sum + num);
System.out.println(result);  // 15
```

また、文字列を扱う場合には下記のように文字列の結合を行うことが可能です。

```java
String result = Arrays.asList("Apple", "Orange", "Peach", "Apricot")
              .stream()
              .reduce("", (str, word) -> str + word + " ");
System.out.println(result.trim());  // "Apple Orange Peach Apricot"
```

# parallelStreamによる並列処理

Stream APIには、parallelStream() という並列処理機能があります。大量のデータを扱うときや、複雑な計算を行うときに処理の効率化を行うことができます。

例えば、parallelStreamを使うと、並列処理を行って処理速度を向上させることができます。
利用する際はparallelメソッドを呼び出すだけで実装することができます。

```java
long count = IntStream.range(1, 1000)
                      .parallel()
                      .filter(n -> n % 2 == 0)
                      .count();
System.out.println(count);  // 499
```

## 注意点

・**小さなデータには不向き**

並列処理は多くのスレッドを利用するため、少量のデータではスレッド間の切り替えのオーバーヘッドが影響し、逆にパフォーマンスが低下することがあります。

・**順序が保証されない**

並列処理では、要素の処理順序が保証されない場合があります。順序が重要な場合は forEachOrdered() を使うことで順序の保証をすることができます。

# まとめ

Stream APIを活用することで、リストを扱う際に簡潔な記述で必要な処理結果を得ることができます。

ただし、無理に使用することでコードが複雑化する場合もあるため、目的が読み取りやすいような実装を心掛けることが必要です。

## エンジニア募集

Gakken LEAP では教育をアップデートしていきたいエンジニアを絶賛大募集しています！
ぜひお気軽にカジュアル面談へお越しください！
https://gakken-leap.co.jp/recruit/