ラムダ式の実践例と応用

## はじめに
本記事ではJavaのラムダ式および関数型インタフェースについて、実践的な使用例を通じた活用方法について説明していきます。

ラムダ式の基本とその背景について知りたい方は、こちらの記事を参考にしてください。
[ラムダ式の基本とその背景](https://qiita.com/pomecode/private/c1bcef34c6c6f48e311d)

また、主要な関数型インタフェースについて知りたい方は、こちらの記事を参考にしてください。
[主要な関数型インタフェースと役割](https://qiita.com/pomecode/private/f4e7efbf71ce2b5e3f8d)

## 対象者
・ラムダ式および関数型インタフェースを初めて学ぶ初心者
・基礎的な知識を習得済みで、再確認や実践例を通じて理解を深めたい方

## ラムダ式を使って「どう便利なのか」
### 日常的に使えるラムダ式の例
ここでは、日常的に使えるラムダ式の例としてスレッド処理とリストの並び替えについて記載します。

#### スレッド処理
[前回の記事](https://qiita.com/pomecode/private/f4e7efbf71ce2b5e3f8d#runnable%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9)の **【Runnableの具体例②】** でもマルチスレッドによるタスク実行について触れましたが、時間のかかる重い処理を例に考えてみましょう。
シングルスレッドの場合は1つのタスクしか実行できないため、それ以外の処理はすべて中断され、待機することになります。
しかし、マルチスレッドの場合は複数のスレッドが並列あるいは並行して処理を実行できるため、 非同期処理やリソース待ち時間の効率的な利用、応答性の向上といったメリットがあります。
※スレッドの競合やスレッド数の増大には注意が必要

以下は、重い処理をスレッドで分割して並列実行する例になります。

**【ラムダ式の実践例①-重い処理の実行】**
```java
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        // 重いタスクを複数のスレッドで並列実行
        IntStream.range(1, 4).forEach(i -> {
            new Thread(() -> {
                System.out.println("Thread " + i + " starts processing.");
                heavyTask(i);
                System.out.println("Thread " + i + " finished processing.");
            }).start();
        });
    }

    private static void heavyTask(int n) {
        try {
            // 2秒スリープ
            Thread.sleep(2000);
            System.out.println("Heavy task " + n + " is done.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```

**【出力結果】**
```
Thread 2 starts processing.
Thread 3 starts processing.
Thread 1 starts processing.
Heavy task 2 is done.
Thread 2 finished processing.
Heavy task 3 is done.
Thread 3 finished processing.
Heavy task 1 is done.
Thread 1 finished processing.
```
※スレッドの実行順序やタイミングは保証されません。（スケジューリングによるため）

#### リストの並び替え
ラムダ式を利用することで、従来よりも簡潔にコードを書いてリストをソートすることができます。
以下のコードでは、`Collections.sort()`メソッドを使用していますが、このメソッドは2つ目の引数に`Comparator`型のラムダ式を受け取るため、2つの要素を比較するための条件を簡潔に記述することができます。

**【ラムダ式の実践例②-自然順（昇順）の並び替え】**
```java
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // 名前リスト
        List<String> names = Arrays.asList("Java", "JavaScript", "Python", "Go");

        // ラムダ式でリストを昇順に並び替え
        Collections.sort(names, (name1, name2) -> name1.compareTo(name2)); // 【1】

        // 並び替え結果を表示
        System.out.println("昇順の並び替え: " + names);
    }
}
```

**【出力結果】**
```
昇順の並び替え: [Go, Java, JavaScript, Python]
```

補足として、Java 8以降では`List`の`sort()`メソッドを使用して、`Comparator`を直接渡す形で並び替えを行うことができます。`Collections.sort()`よりも簡潔に記述できます。
この場合、上記の【1】の行を以下のように置き換えます。

```java
        names.sort((name1, name2) -> name1.compareTo(name2));
```

さらに、「自然順序が定義されている型（`Comparable`を実装している型）」の比較を行いたい場合は、`Comparator`インタフェースの`naturalOrder()`メソッドを使用すると、さらに簡潔に記述できます。
※上記コードでは`Comparator`をインポートしていないため、`java.util.Comparator`の`import`が必要
※こちらはラムダ式を使用していませんが、簡潔に記載できる方法のため載せておきます。

```java
        names.sort(Comparator.naturalOrder());
```

また、オブジェクトの特定のフィールドに基づいて並び替えを行うことも可能です。
以下の例では、`Person`クラスの`age`フィールドを用いてオブジェクトのリストをソートしています。

**【ラムダ式の実践例③-自然順（昇順）の並び替え（カスタムオブジェクト）】**
```java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class Person {
    String name;
    int age;

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

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

public class Main {
    public static void main(String[] args) {
        // 名前リスト
        List<Person> people = new ArrayList<>();
        people.add(new Person("Amy", 23));
        people.add(new Person("Bob", 38));
        people.add(new Person("Carol", 24));
        people.add(new Person("David", 30));

        // 年齢で昇順に並び替え
        people.sort((p1, p2) -> p1.age - p2.age);

        // 昇順並び替え結果を表示
        System.out.println("年齢で昇順: " + people);
    }
}
```

**【出力結果】**
```
年齢で昇順: [Amy (23), Carol (24), David (30), Bob (38)]
```

### Stream APIとの組み合わせ
ラムダ式はStream APIと組み合わせて使用することで、簡潔かつ可読性の高いコードを書くことができます。
Stream APIについて簡単に説明すると、コレクションや配列などのデータ処理を簡潔に記述するためのAPIで、データの生成・中間操作・終端操作の流れで、メソッドチェーンを利用して処理を組み合わせることが可能です。

以下は、整数値のリストから偶数だけを抽出し、それを2倍にして表示する例になります。

**【ラムダ式の実践例④-Stream APIとの組み合わせ】**
```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> doubledEvenNumbers = numbers.stream()
                                                  .filter(n -> n % 2 == 0)       // 偶数を抽出
                                                  .map(n -> n * 2)               // 各要素を2倍
                                                  .collect(Collectors.toList()); // 結果をリストに変換

        System.out.println(doubledEvenNumbers);
    }
}
```

**【出力結果】**
```
[4, 8, 12, 16, 20]
```

はじめに、`numbers.stream()`でリストを`Stream`に変換し、中間操作として`filter`と`map`で加工を行った後、終端操作として`collect`で要素をリストに変換しています。
`filter`メソッドは`Predicate`型の引数を受け取るため、条件に合致する要素だけを残します。
また、`map`メソッドは`Function`型の引数を受け取るため、受け取った要素に何らかの処理を加えて返却することができます。

### メソッド参照
最後に、メソッド参照についても触れておきたいと思います。
メソッド参照とは既存メソッドを簡潔に呼び出す方法で、ラムダ式をより簡略化した形といえます。

#### メソッド参照の種類
Javaでは以下の4種類のメソッド参照があります。
1. 静的メソッド参照 ⇒ クラス名::メソッド名
2. インスタンスメソッド参照 ⇒ インスタンス::メソッド名
3. 型によるインスタンスメソッド参照 ⇒ クラス名::メソッド名
4. コンストラクタ参照 ⇒ クラス名::new

#### ラムダ式とメソッド参照の比較
ラムダ式を使用すると次のような形になりますが、場合によってはメソッド参照で更に簡潔に書くことができます。
例として、リストの要素を順番に出力すると処理をあげます。

**【ラムダ式の場合】**
```java
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Amy", "Bob", "Carol");
        names.forEach(name -> System.out.println(name)); // 【1】
    }
}
```

上記【1】の行で使用しているラムダ式は、受け取った引数をそのまま表示するだけの単一のメソッド呼び出しのため、メソッド参照に置き換えることが可能です。
※`System.out.println`は静的メソッドであるため、クラス名::メソッド名で記載

**【メソッド参照の場合】**
```java
        names.forEach(System.out::println);
```

**【出力結果】**
```
Amy
Bob
Carol
```

#### メソッド参照の実践例
上記、[リストの並び替え](https://qiita.com/pomecode/private/970cfb46415a744ae80b#%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E4%B8%A6%E3%81%B3%E6%9B%BF%E3%81%88)で例にあげた **【ラムダ式の実践例②-自然順（昇順）の並び替え】** で使用しているラムダ式はメソッド参照に置き換えることができます。


**【ラムダ式の実践例⑤-メソッド参照】**
**【元のコード-ラムダ式の場合】**
```java
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // 名前リスト
        List<String> names = Arrays.asList("Java", "JavaScript", "Python", "Go");

        // ラムダ式でリストを昇順に並び替え
        Collections.sort(names, (name1, name2) -> name1.compareTo(name2)); // 【1】

        // 並び替え結果を表示
        System.out.println("昇順の並び替え: " + names);
    }
}
```

【1】の行をメソッド参照に置き換えると以下のようになります。
**【修正後-メソッド参照の場合】**
```java
        Collections.sort(names, String::compareTo);
```

上記は「型によるインスタンスメソッド参照」に当てはまります。
元のラムダ式 `(name1, name2) -> name1.compareTo(name2)` は`String`型のインスタンスのメソッド`compareTo`を参照しているため、`String::compareTo`に置き換えることが可能です。