Stream API入門

# 導入

JavaのStream API（ストリームAPI）は、コレクションなどのデータソースに対してパイプライン処理を行うための強力な機能を提供してくれます。これを使うことで、**データを連続的に処理**しながら、各ステップを**直感的に記述**できます。
本記事では、Stream APIの基本を入門レベルでわかりやすく解説します。

# Stream APIの概要

## パイプライン処理

Stream APIのメインになるのはパイプライン処理です。
パイプライン処理とは、`ある処理の出力が次の処理の入力`となり、一連の処理が**連鎖的**に行われる仕組みです。
例えば、データをフィルタリングしてから、変換し、最後に集計する、といった一連の処理をパイプラインとして記述できます。

## ストリームの基本構造

ストリームの処理は次の3段階に分かれます。

1. **ストリーム生成**
2. **中間操作**
3. **終端操作**

以下の例ではこれらの3段階のステップが実行されています。

```java
List<Integer> list = Arrays.asList(10, 3, 1, 8, 3, 2, 6, 7, 10, 6);
long count = list.stream() // → ストリーム生成
    .filter(x -> x % 2 == 0) // → 中間操作1: 偶数のみ抽出 (10, 8, 2, 6, 10, 6)
    .distinct() // → 中間操作2: 重複排除 (10, 8, 2, 6)
    .count(); // → 終端操作: 要素の個数をカウント
System.out.println(count);
// [4]
```
上記のように1つの処理の中で複数のステップを実行していることがわかると思います。

## ステップ1: ストリームの生成

ストリームは、コレクションや配列など、さまざまなデータソースから生成できます。
ストリームの生成には、以下の方法があります。

### 配列からのストリーム生成

配列をストリームにする際は`Arrays.stream`メソッドを使います。

```java
String[] strArr = {"aaa", "bbb", "ccc"};
Arrays.stream(strArr) // 配列からストリームを生成
    .forEach(System.out::println);
```

### コレクションからのストリーム生成

コレクションをストリームにする際は`Collection.stream`メソッドを使います。

```java
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numList.stream() // コレクションからストリームを生成
    .forEach(System.out::println);
```

### `Stream`クラスの静的メソッド

その他のストリーム生成の方法です。
`Stream.of`、`Stream.generate`、`Stream.iterate`、`Stream.concat`などの静的メソッドを使ってストリームを生成することができます。

```java
// 単一要素から生成
Stream<String> singleElementStream = Stream.of("apple");

// 無限ストリームの生成
Stream<Double> randomNumbers = Stream.generate(Math::random)
    .limit(5); // 制限なくストリームを生成し、5つに切り抜く
randomNumbers.forEach(System.out::println);

```

### プリミティブ型ストリーム

Intなどのプリミティブ型に対しては、`IntStream.range`と`IntStream.rangeClosed`などを使って整数の範囲を指定してストリームを生成できます。

```java
IntStream.rangeClosed(1, 5)
    .forEach(System.out::println);
    // [1, 2, 3, 4, 5]
```

## ステップ2: 中間操作

中間操作はストリームを変換するステップです。複数回実行することが可能です。
代表的な中間操作を見てみましょう。

### `filter`

指定した条件に合致する要素だけを含むストリームを生成する中間操作です。

```java
List<String> list = Arrays.asList("apple", "banana", "cherry", "blueberry");
Stream<String> filteredStream = list.stream()
    .filter(s -> s.startsWith("b")); // (banana, blueberryのみにフィルタ)
```

### `map`

各要素に対して何らかの処理を施し、その結果を新しいストリームとして返す中間操作です。

```java
List<String> list = Arrays.asList("apple", "banana", "cherry");
Stream<String> upperCaseStream = list.stream()
    .map(String::toUpperCase); // 全て大文字に変換する
```

### `distinct`

重複する要素を除いたストリームを生成する中間操作です。

```java
List<Integer> list = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
Stream<Integer> distinctStream = list.stream()
    .distinct(); // (1, 2, 3, 4, 5)
```

## ステップ3: 終端操作

終端操作はストリームのデータに対して最終的な処理を行います。
終端操作はストリームの処理を完了し、結果を生成するため、一連の処理の中で生成されたストリームは再利用できなくなります。

### `forEach`
各要素に対してアクションを実行します。

```java
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.stream()
    .forEach(System.out::println);
    // apple
    // banana
    // cherry
```

### `collect`
ストリームの要素をコレクションなどに変換します。

```java
List<String> list = Arrays.asList("apple", "banana", "cherry", "blueberry");
List<String> resultList = list.stream()
    .filter(s -> s.startsWith("b"))
    .collect(Collectors.toList());
    // resultListは["banana", "blueberry"]のリストになる
```

### `count`
ストリーム内の要素の個数を返します。

```java
List<String> list = Arrays.asList("apple", "banana", "cherry");
long count = list.stream()
    .count();
    // [3]
```

# 並列ストリーム

## 順次ストリームと並列ストリーム

- **順次ストリーム**: 要素を1つずつ順番に処理するストリームです。元のリストの順序が保持されます。
- **並列ストリーム**: マルチスレッドで要素を並列に処理するストリームです。処理順序は保証されませんが、大規模データの処理に有利です。


### 順次ストリームの例

```java
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream()
    .forEach(System.out::println); 
    // 順番に出力 1, 2, 3, 4, 5
```

### 並列ストリームの例
並列ストリームを生成するには、`parallelStream`メソッドを使用します。

```java
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.parallelStream()
    .forEach(System.out::println);
    // 順番は不定 2, 4, 5, 3, 1 など
```

# 終わりに
この記事では、JavaのStream APIを使ったパイプライン処理の基本を紹介しました。ストリームを使えば、データの処理が簡潔かつ効率的に記述できます。
是非参考にしてみてください。