Javaのラムダ式とStreamAPIとは

## 前提

以降の説明で表示するソースは全て [GitHub](https://github.com/thirdplay/java-stream) に上げてあります。

---
## 本記事の目的
`Java8` で追加された機能（関数型プログラミング・ラムダ式・Stream）というものについて、
難しい説明を極力省き、下記のような記述を読みこなせるようになるのが目標です。

```java
persons.stream()
       .filter(p -> p.getAge() >= 30)
       .forEach(p -> System.out.println(p.getName()));
```

---
## ラムダ式とは
インターフェースに `抽象メソッド` を1つだけ定義したものを `関数型インターフェース` と呼びます。
この `関数型インターフェース` の実装を、とことん簡潔に記述するのがラムダ式です。

---
たとえば、こんなインターフェースがあるとします。

```java:Sample01.java
interface Iface1 {
  void method(int i, String s);
}
```

---
### 通常クラスの場合

```java:Face1.java
class Face1 extends Iface1 {
  public void method(int i, String s) {
    System.out.println(i + s);
  }
}
```

```java
Iface1 if1 = new Face1();
```

---
### 無名クラスの場合
無名クラスとは、一時的に利用するために作られる名前の無いクラスです。
インスタンスの生成と同時にクラス定義も指定します。

```java:Sample01.java
Iface1 if1 = new Iface1() {
  public void method(int i, String s) {
    System.out.println(i + s);
  }
};
```

---

### 簡単です。簡単ですけど

---
### まだまだ冗長です

---
### もっと簡潔に書けないでしょうか？

---
簡潔に書く上でポイントとなるのが `関数型インターフェース` の特徴です。
`関数型インターフェース` には `抽象メソッド` が1つしかありません。
1つだけならメソッド名をわざわざ実装側に書かなくてもコンパイラには判るはずです。
同様に引数の型や戻り値もコンパイラには判るはず。

そういったコンパイラには判るはずの情報を省略して必要な部分だけ記述するのが `ラムダ式` です。

---
### ラムダ式の場合
```java
Iface1 if1 = (i, s) -> System.out.println(i + s);
```

矢印（->）をはさんだ左側が引数で、右側が処理になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38694/c4c8d1a5-2cea-80d2-226e-6fe7ee089036.png)

---
### 処理が複数行ある場合
処理が複数行に渡る場合は `{}` で括ります。

```java:Sample01.java
Iface1 if1 = (i, s) -> {
  System.out.println(i);
  System.out.println(s);
};
if1.method(123, "xyz"); 
// => 123
// => xyz
```

---
### ラムダ式の使いどころ
List を例として、ラムダ式を使うケースを見てみます。
List のデータを処理する場合、ほぼ必ずループが出てきます。

```java:Sample02.java
List<Integer> list = Arrays.asList(100, 200, 300, 400, 500);
for (Integer i: list) {
  // 1件ごとの処理
}
```
これはこれでいいのですが、Java8からは `forEach` メソッドを使うことでより簡潔に書くことができます。

```java
list.forEach(/*1件ごとの処理*/)
```
上記で全データに対してカッコ内の処理が行われます。

forEach の引数として `1件ごとの処理` を渡していますが、これは具体的に何を指定するのか。
ラムダ式とどんな関係があるのか見ていきましょう。

---
### 一件ごとの処理の渡し方
`forEach` に渡すのは `Consumer` というインターフェースのオブジェクトです。
`Consumer` には `accept` というメソッドが定義されており

```java
public interface Consumer<T> {
  void accept(T t);
```
この `accept` に1件ごとの処理を記述しておきます。

---
実際に `Consumer` を実装してみます。

```java:Sample02.java
List<Integer> list = Arrays.asList(100, 200, 300, 400, 500);
list.forEach(new Consumer<Integer>() {
  @Override
  public void accept(Integer i) {
    System.out.println(i);
  }
});
// => 100 200 300 400 500 （改行あり）
```

上記で見たとおり `Consumer` で実装する抽象メソッドは `accept` だけです。
抽象メソッドが1つしかないインターフェース（関数型インターフェース）は `ラムダ式` で書けます。
そうするとコードは下記のようになり、かなりスッキリします。

```java:Sample02.java
List<Integer> list = Arrays.asList(100, 200, 300, 400, 500);
list.forEach(i -> System.out.println(i));
// => 100 200 300 400 500 （改行あり）
```

---
### （参考）実行時の forEach の動き
`forEach` は、各データを引数として `Consumer` の `accept` を呼び出してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38694/a2524ec8-bc7a-ceb8-6d61-c27e697a29fa.png)
実装例として、全データを画面表示させてみましたが、実際のプログラミングはそんな単純なことばかりではなく、条件判定を行ったり計算したり、いろいろあるはずです。
そういったいろいろな処理を行うためのメソッドや関数型インターフェースが `Java8` では用意されており `StreamAPI` として提供されています。

---
## StreamAPI
`StreamAPI` というのは、コレクションや配列、数字の集合、ファイル一覧など、データのかたまりを操作するためのライブラリです。
コレクションを `StreamAPI` で操作するのは非常に簡単で、前項のサンプル `list` を例にとると

```java
list.stream();
```
で `Stream` が生成され、以降の処理は `Stream` のメソッドを呼び出すことで実行します。
様々なメソッドが `Stream` には用意されており、データ処理が簡単に行えます。

前項の `forEach` はラムダ式との組み合わせで動作しましたが、
`Stream` のメソッドもラムダ式と組み合わせて使用するものが多数あります。

---
### 中間操作と終端操作
`Stream` には `中間操作` と `終端操作` という2種類のメソッドがあります。
`中間操作` は `Stream` の抽出や変換を行い、新たな `Stream` を生成します。
`終端操作` は `Stream` に対する最終的な処理（合計算出やリスト化など）を行います。

---
文字だけでは分かりにくいので図で描いてみます。
たとえば、ここに性別と年齢のデータがリスト形式であるとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38694/d25ea72e-1df0-cb30-8779-9feaac872b9e.png)
ここから男性の合計年齢を計算する場合

　①リストから Stream を生成
　②そこから男性だけを抽出して Stream を生成
　③そこから年齢だけを取り出して Stream を生成
　④その合計を求める

という流れになり、②と③が中間操作で④が終端操作に相当します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38694/64ea632e-a80f-c5f9-ca6f-017c5ce6cb1b.png)

---
`Stream` を生成し、その `Stream` を元に新たな `Stream` を生成するという作業を中間操作でくり返し、
終端操作で最終的なゴール（男性の合計年齢）に到達しています。コーディングは次のような形になります。

```java
// コレクション.stream().中間操作().中間操作().終端操作()
// （中間操作の戻り値は Stream なので処理を次々つないでいける。）
list.stream().filter((x) -> x.getGender() == Gender.MALE).mapToInt((x) -> x.getAge()).sum();
```
中間操作、終端操作はすべて `Stream` のメソッドとして提供されています。

---
### 抽出
条件を指定して抽出を行うのが `filter` メソッドです。

```java
コレクション.stream().filter (抽出ロジック)
```
と書けば抽出後の `Stream` が `filter` から返却されます。
`filter` の引数が `抽出ロジック` となっていますが、それはどういうことかというと、`filter` に渡す引数は `Predicate` という関数型インターフェースと決まっており、そこに抽出ロジックを記述します。
`Predicate` には `test` というメソッドがあり

```java
public interface Predicate<T> {
  boolean test(T t);
```
この `test` メソッドに抽出の条件判定ロジック（true／falseを返す）を記述します。

---
下記サンプルは、数値を `300` 以上という条件で抽出しています。
`Predicate` を `無名クラス` で実装したものと `ラムダ式` を使ったものと2パターンです。

#### 無名クラス
```java:Sample03.java
List<Integer> list = Arrays.asList(100, 200, 300, 400, 500);
list.stream().filter(new Predicate<Integer>() {
  @Override
  public boolean test(Integer i) {
    return i >= 300;
  }
}).forEach(i -> System.out.println(i));
// => 300 400 500 （改行される）
```

#### ラムダ式
```java:Sample03.java
List<Integer> list = Arrays.asList(100, 200, 300, 400, 500);
list.stream().filter(i -> i >= 300)
             .forEach(i -> System.out.println(i));
// => 300 400 500 （改行される）
```
`filter` に渡したラムダ式は `test` メソッド本体として扱われます。
一行で書ける場合は `return` が省略可能 です。

---
#### （参考）実行時の filter の動き
`filter` は `Stream` の各データを引数として `Predicate` の `test` を呼び出し、`true` となるデータで `Stream` を新たに生成し、戻り値とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38694/96c86392-42d5-fb3d-e446-fa64d9a18d47.png)

---
### 集計
`Stream` の集計を行うときは `reduce` というメソッドを使います。
しかし、この `reduce` はちょっと分かりにくいので別の方法で実行します。
`IntStream` というものを使うのですが、`IntStream` とはそもそもなんなのか、それをさらっと説明します。

これまで `Stream` とひとくちに言ってきましたが、実は `Stream` には下記の4種類あり、その中のひとつが `IntStream` です。

* Stream<T>
* IntStream
* LongStream
* DoubleStream

前項のサンプル `list.stream()` で生成されるのは `Stream<T>` です。
`Stream<T>` は言わば汎用的な `Stream` で、それに比べて `IntStream` , `LongStream` , `DoubleStream` は、数値（int、long、double）専用の `Stream` です。
平均を求める `average` や 合計を求める `sum` といったメソッドが用意されており、集計を非常に簡単に行えます。

---
使い方の手順として、いったん `Stream<T>` を生成し、それを `IntStream` に変換します。
変換には `mapToInt` というメソッドを使います。
たとえば、元データとして数値文字列のリストがあるとします。集計をとる際の流れは

　① `list.stream()` で `Stream<String>` を生成し
　②そのインスタンスの `mapToInt` メソッドで `IntStream` を生成し
　③ `IntStream` の `sum` メソッドで合計を求める

となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38694/ba16e5dd-5368-842b-7532-25c6e62d7452.png)

---
#### mapToInt
`mapToInt` には `ToIntFunction` という関数型インターフェースを渡します。
`ToIntFunction` には `applyAsInt` というメソッドがあり

```java
public interface ToIntFunction<T> {
  int applyAsInt(T value);
}
```
この `applyAsInt` メソッドに 元データ → `int` への変換プログラムを記述します。
下記は合計を求めるサンプルですが `ToIntFunction` を `無名クラス` で実装したものと `ラムダ式` を使ったものと2パターンです。

#### 無名クラス

```java:Sample04.java
List<String> list = Arrays.asList("100", "200", "300", "400");
int sum = list.stream().mapToInt(new ToIntFunction<String>() {
  @Override
  public int applyAsInt(String s) {
    return Integer.parseInt(s);  // 変換
  }
}).sum();
System.out.println(sum);
// => 1000
```

#### ラムダ式

```java:Sample04.java
List<String> list = Arrays.asList("100", "200", "300", "400");
int sum = list.stream()
  .mapToInt(s -> Integer.parseInt(s))
  .sum();
System.out.println(sum);
// => 1000
```

---
#### （参考）実行時の mapToInt の動き
`mapToInt` は `Stream<T>` の各データを引数として `ToIntFunction` の `applyAsInt` を呼び出し、変換された `int` の値で `IntStream` を生成し戻り値とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38694/5539c67e-93bf-9a63-41b4-30be4180d4cc.png)

#### filter と一緒に
`filter` と組み合わせれば、条件を指定して集計できます。 `300` 以上の値を合計してみます。

```java:Sample05.java
List<String> list = Arrays.asList("100", "200", "300", "400");
int sum = list.stream()
  .mapToInt(s -> Integer.parseInt(s))
  .filter(i -> i >= 300)
  .sum();
System.out.println(sum);
// => 700
```

---
#### Stream をリストへ戻す
ここまでは、リストを `Stream` に変換して操作をする方法を見てきましたが、今度は逆に `Stream` をリストに変換する方法を見てみます。

```java:Sample05.java
List<Integer> list = Arrays.asList(100, 200, 300, 400, 500);
List<Integer> list2 = list.stream()
  .filter(i -> i >= 300)
  .collect(Collectors.toList());
for (Integer i : list2) {
  System.out.println(i);
}
// 300 400 500  （改行あり）
```
`collect(Collectors.toList())` で `filter` 後の `Stream` がリストに変換されています。

---
## まとめ
### ラムダ式
インターフェースに `抽象メソッド` を1つだけ定義したものを `関数型インターフェース` と呼ぶ。
この `関数型インターフェース` の実装を、とことん簡潔に記述するのがラムダ式。

### StreamAPI
コレクションや配列、数字の集合、ファイル一覧など、データのかたまりを操作するためのライブラリ。