入門Javaのラムダ式とStream API

# 環境
- JDK 17

# はじめに
例えば、こんな`Member`レコードがあって、

```java
public record Member(String name, int age) {
}
```

こんな`List<Member>`があるとします。

```java
List<Member> memberList = List.of(
  new Member("佐々木久美", 27),
  new Member("金村美玖", 20),
  new Member("髙橋未来虹", 19),
  new Member("正源寺陽子", 16)
);
```

この`memberList`から「18歳以上のメンバーの名前だけのリスト」を作りたい場合、どうしましょうか？

ベタに書くならこんな感じになります。

```java
List<String> resultList = new ArrayList<>();
for (Member member : memberList) {
  if (member.age() >= 18) {
    String name = member.name();
    resultList.add(name);
  }
}
```

しかし、このコードには次のような問題があります。

- 何がやりたいのかを理解するには、コードを詳細に読む必要がある
- forやifの多用はバグを生みやすい

そこで、Stream APIの登場です。Stream APIは、`List`などのコレクションに対して抽出や変換を行うAPIです。Stream APIを使うことで、

- 何がやりたいのかすぐに理解できる
- forやifが不要なのでバグを生みにくい

というメリットが得られます。

具体的にはこんな感じです（後で詳細に解説します）。

```java
// ListをStreamに変換する
List<String> resultList = memberList.stream()
  // １８歳以上のメンバーのみ抽出する
  .filter(member -> member.age() >= 18)
  // MemberからString（メンバーの名前）に変換する
  .map(member -> member.name())
  // Listに変換する
  .toList();
```

`.stream().filter(...).map(...).toList()`の部分がStream APIです。

そして、`filter()`・`map()`の引数`member -> member.age() >= 18`・`member -> member.name()`の部分がラムダ式です。

なので、Stream APIを理解するには、まずはラムダ式を理解する必要があります。

# ラムダ式
## 匿名クラスとは
ラムダ式を理解するには、まずは匿名クラスを理解する必要があります。ちょいちょい使う割にはJava入門書などではあまり説明されていないので、ここで説明します。

次のような`Hoge`インタフェースがあるとします。

```java:インタフェース
interface Hoge {
  int doSomething(String str);
}
```

このインタフェースを使いたい場合、通常だと実装クラスを作りますね。

```java:実装クラス
class HogeImpl implements Hoge {
    @Override
    public int doSomething(String str) {
        return str.length();
    }
}
```

```java:通常の書き方
Hoge hoge = new HogeImpl();
int length = hoge.doSomething("あいうえお");
System.out.println(length);  // "5"と出力される
```

しかし、この`HogeImpl`クラスは1箇所でしか使わない場合は、クラスを作るのが大げさな感じがします。

そこで、前出のような通常のクラス定義を行わずに`Hoge`インタフェースを使う方法があります。それが匿名クラスです。見た目は違いますが、前出のコードと全く同じことをしています。

```java:匿名クラスを使った書き方
// これが匿名クラス
Hoge hoge = new Hoge() {
  @Override
  public int doSomething(String str) {
    return str.length();
  }
};
int length = hoge.doSomething("あいうえお");
System.out.println(length);  // "5"と出力される
```

クラス定義をしなくてもいいので楽ですね！


しかし一方で、何となくゴチャゴチャしている感じがします。何故かと言うと、推測できるはずの情報も書いているからです。

- 右辺の`new Hoge()`は、左辺に`Hoge`と書いてあるから推測可能なはず
- `Hoge`インタフェースには抽象メソッドが1つしか無いので、メソッド名`doSomething`・戻り値の型`int`・引数の型`String`は推測可能なはず

## ラムダ式
そこで、ラムダ式の登場です。ラムダ式は、匿名クラスの推測可能な部分を省略した書き方です。

> 文法的に正確に言うとラムダ式は匿名クラスの省略記法**ではない**のですが、使う上では省略記法と思っても大丈夫です。

```java:（再掲）匿名クラスを使った書き方
// これが匿名クラス
Hoge hoge = new Hoge() {
  @Override
  public int doSomething(String str) {
    return str.length();
  }
};
int length = hoge.doSomething("あいうえお");
System.out.println(length);  // "5"と出力される
```

```java:ラムダ式を使った書き方
// これがラムダ式。やっていることは匿名クラスの場合と全く同じです。
Hoge hoge = (str) -> {
  return str.length();
};
int length = hoge.doSomething("あいうえお");
System.out.println(length);  // "5"と出力される
```

> 引数名は任意です。つまり、今回だと`str`でなくても構いません。好きな名前を付けてOKです。

ただし、全ての匿名クラスをラムダ式で書き換えられる訳ではありません。ラムダ式で書き換えられるのは、「関数型インタフェースの匿名クラスのみ」です。


関数型インタフェースとは、平たく言うと「抽象メソッドを1つだけ持つインタフェース」です（抽象メソッドの他に、defaultメソッドやstaticメソッドがあっても構いません）。


関数型インタフェースには`@FunctionalInterface`アノテーションを付加することができます。

```java
@FunctionalInterface
interface Hoge {
  int doSomething(String str);
}
```

> `@FunctionalInterface`アノテーションを付加しているのに抽象メソッドが2つ以上あった場合などは、コンパイルエラーになります。

## ラムダ式の省略
ラムダ式には、更なる省略記法があります。

- 引数が1つだけの場合、`()`は省略できる
    - 引数が2個以上や0個の場合は省略不可
- `{}`内の処理が1行だけの場合、`{}`は省略できる
    - 処理が2行以上の場合は省略不可
- 処理が`return ○;`だけの場合、`return`・`;`は省略できる

ということで、省略前と省略後のラムダ式を並べるとこんな感じです。

```java:省略前のラムダ式
Hoge hoge = (str) -> {
  return str.length();
};
```

```java:省略後のラムダ式
Hoge hoge = str -> str.length();
```

一番よく使う書き方は、このような省略後の書き方です。少しずつ練習していきましょう。

## メソッド参照
実は、ラムダ式には更なる省略記法のようなものがあります。それがメソッド参照です。

```java:ラムダ式
Hoge hoge = str -> str.length();
```

```java:メソッド参照
Hoge hoge = String::length;
```

ただし、個人的には分かりづらいなと思うので、あまりメソッド参照は使いません。

# Stream API
お待たせしました。ようやっとStream APIの登場です。Stream APIは、`List`などのコレクションに対して抽出や変換を行うAPIです。

## Streamの生成
Stream APIの主役は`java.util.stream.Stream`インタフェースが主役です。このインタフェースに、値を抽出・変換したり、最終結果を`List`などに変換するメソッドが定義されています。

なので、まずは`List`から`Stream`を作る必要があります。`List`の`stream()`で`Stream`を生成できます。

```java:ListからStreamを生成
List<Member> memberList = List.of(
  new Member("佐々木久美", 27),
  new Member("金村美玖", 20),
  new Member("髙橋未来虹", 19),
  new Member("正源寺陽子", 16)
);

// Streamを生成
Stream<Member> stream = memberList.stream();
```

配列から`Stream`を作る場合は、`java.util.Arrays`クラスの`stream()`メソッドを利用します。

```java:配列からStreamを生成
Member[] members = {
  new Member("佐々木久美", 27),
  new Member("金村美玖", 20),
  new Member("髙橋未来虹", 19),
  new Member("正源寺陽子", 16)
};

// Streamを生成
Stream<Member> stream = Arrays.stream(members);
```

## 中間操作
中間操作は、生成した`Stream`に対する要素の抽出や変換を行うメソッドです。中間操作は、0回以上行うことができます。


特によく使う中間操作は、`filter()`と`map()`です。

### 要素の抽出
要素の抽出を行えるのは`filter()`メソッドです。

```java:
// １８歳以上のメンバーのみを抽出したStreamを作成
Stream<Member> filterredStream = stream.filter(member -> member.age() >= 18);
```

`filter()`の引数はラムダ式になっているので、これは関数型インタフェースのはずです。

[`filter()`のメソッド定義](https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html#filter(java.util.function.Predicate))を見ると、引数の型は`java.util.function.Predicate`になっています。これは、抽象メソッドが`boolean test(T t)`のみの関数型インタフェースです。


今回の場合は`T`が`Member`になっていると考えてください。つまり、このメソッドは`Member`を引数にとって`boolean`を返します。今回では`member.age() >= 18`（つまり年齢が18歳以上なら`true`）を返しています。

### 要素の型変換
要素の型変換を行えるのは`map()`メソッドです。

```java
// メンバー（Member）のStreamから、名前（String）のStreamに変換
Stream<String> mappedStream = filterredStream.map(member -> member.name());
```

`map()`の引数はラムダ式になっているので、これは関数型インタフェースのはずです。

[`map()`のメソッド定義](https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html#map(java.util.function.Function))を見ると、引数の型は`java.util.function.Function`になっています。これは、抽象メソッドが`R apply(T t)`のみの関数型インタフェースです。


今回の場合は`T`が`Member`・`R`が`String`になっていると考えてください。つまり、このメソッドは`Member`を引数にとって`String`を返します。今回では`member.name()`（つまりメンバーの名前）を返しています。

### その他の中間操作
中間操作は他にもたくさんあります。詳細は[`Stream`のJavadoc](https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html)をご確認ください。

## 終端操作
終端操作は、`Stream`を最終目的となる型（`List`など）に変換するメソッドです。終端操作は、1回のみ行うことができます。

### `List`への変換
`Stream`から`List`への型変換を行えるのは`toList()`メソッドです。

```java
Stream<String> mappedStream = ...;
// Listに変換する終端操作
List<String> resultList = mappedStream.toList();
```

ちなみに、この`resultList`はイミュータブル（不変）です。例えば、この後に`resultList.add("平尾帆夏")`とすると例外がスローされます。

> `toList()`メソッドはJava 16で導入されました。それより前のバージョンのJavaを使っている場合は`mappedStream.collect(Collectors.toList())`と書く必要があります。ただしこの場合、戻り値の`List`がミュータブル（可変）になります。

### その他の終端操作
終端操作は他にもたくさんあります。詳細は[`Stream`のJavadoc](https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html)をご確認ください。

## メソッドチェーンでの書き方
ここまでは、`Stream`の生成・要素の抽出・要素の変換・`List`への変換をすべて1行ずつ書いていました。

```java:1行ずつ書く場合
// 元のList
List<Member> memberList = ...;
// Streamを生成
Stream<Member> stream = memberList.stream();
// 要素を抽出
Stream<Member> filterredStream = stream.filter(member -> member.age() >= 18);
// 要素を変換
Stream<String> mappedStream = filterredStream.map(member -> member.name());
// List<String>に変換
List<String> resultList = mappedStream.toList();
```

しかし、実際によく使うのはメソッドチェーンでの書き方です。すなわち、メソッドの戻り値をいちいち変数に代入せずに、`.`でつなぎます。

```java:メソッドチェーンで書く場合
// 元のList
List<Member> memberList = ...;
// メソッドチェーンで記述
List<String> resultList = memberList.stream()  // Streamを生成
  .filter(member -> member.age() >= 18)  // 要素を抽出
  .map(member -> member.name())  // 要素を変換
  .toList();  // List<String>に変換
```

最初に学習する段階では、`List<Member>`→`Stream<Member>`→`Stream<String>`→`List<String>`の型変換が分かりやすいように、1行ずつ変数に代入することをおすすめします。理解が進んできたら、メソッドチェーンで書きましょう。もちろん、実務ではメソッドチェーンの利用をおすすめします。

# 更に勉強したい人へ
- [`Stream`のJavadoc](https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html)
- [徹底解説！Project Lambdaのすべて リターンズ](https://www.slideshare.net/slideshow/embed_code/key/nnHz05aMkle7hh?startSlide=1)

<iframe src="https://www.slideshare.net/slideshow/embed_code/key/nnHz05aMkle7hh?startSlide=1" width="597" height="486" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px;max-width: 100%;" allowfullscreen></iframe><div style="margin-bottom:5px"><strong><a href="https://www.slideshare.net/bitter_fox/java8-launch" title="徹底解説！Project Lambdaのすべて リターンズ[祝Java8Launch #jjug]" target="_blank">徹底解説！Project Lambdaのすべて リターンズ[祝Java8Launch #jjug]</a></strong> from <strong><a href="https://www.slideshare.net/bitter_fox" target="_blank">bitter_fox</a></strong></div>

- [今こそStream API入門](https://www.slideshare.net/slideshow/embed_code/key/9P5mqlYiLUdIgy?startSlide=1)

<iframe src="https://www.slideshare.net/slideshow/embed_code/key/9P5mqlYiLUdIgy?startSlide=1" width="597" height="486" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px;max-width: 100%;" allowfullscreen></iframe><div style="margin-bottom:5px"><strong><a href="https://www.slideshare.net/skrb/stream-api-125945709" title="今こそStream API入門" target="_blank">今こそStream API入門</a></strong> from <strong><a href="https://www.slideshare.net/skrb" target="_blank">Yuichi Sakuraba</a></strong></div>