Java6しか使ってこなかった人がJava8のStreamAPIに感動した話

僕は前職ではず〜っとJava6という古のバージョンを使っていて、ここ最近転職してからJava11を使っています。

その間にはJava8という素晴らしい機能を輩出したバージョンもあったのに使わず仕舞いでした。

が！最近ようやくJava8のStreamAPIを意図的に使う機会がありまして、とても感動したので、どういうことをやったのかを載せたいと思います。

## この記事でわかること
- Java8のStreamAPIを使って異なるリスト内オブジェクトのキー重複を排除する方法
- ケーススタディでStreamAPIのざっくりとした使い方

## 結論
どんなことをしたかいうと、**重複排除したいリストを起点としてStream変換し、filterメソッドで条件を指定する**です。

何を言っているか、ちんぷんかんぷんな人もいると思いますが安心してください。この後詳しく説明していきますので。

ただ、ここでこんな疑問を持った方もいるかもしれません。

なぜdistinctメソッドを使わないの？

distinctメソッドは、同一リスト内の値に重複がある場合にしか重複排除してくれないので、僕が実現したかったことにはそぐわなかったんですよね。

では、僕が実現したかったこと、実際にやったことなどをこれから説明していきます！

## 僕が実現したかった要件

ある二つのリスト（それぞれ異なるクラスオブジェクトを要素に持つ）から、一方のリスト内オブジェクトのキーとなるフィールドと、もう一方のリスト内オブジェクトのキーとなるフィールドを比較して、キー重複があれば排除されたオブジェクトだけのリストを作りたい。

例えば、従業員情報を格納するEmployeeクラスのリスト（①）と貸与PC端末情報を格納するPcAssetクラス（②）があるとします。

```java:①
List<Employee> employeeList = List.of( 
Employee.builder().employeeId("abc").employeeName("佐藤").build() 
,Employee.builder().employeeId("def").employeeName("鈴木").build() 
,Employee.builder().employeeId("ghi").employeeName("田中").build() 
,Employee.builder().employeeId("jkl").employeeName("高橋").build()
);

@Data 
@Builder 
public static class Employee { 
// 従業員ID 
private String employeeId; 
// 従業員名 
private String employeeName; 
}
```
```java:②
List<PcAsset> pcAssetList = List.of( 
Employee.builder().assetId("pc001").employeeId("abc").build()
, Employee.builder().assetId("pc002").employeeId("ghi").build()
);

@Data 
@Builder 
public static class PcAsset { 
// 資産ID 
private String assetId; 
// 貸与従業員ID 
private String employeeId; 
}
```
①の中からPC端末を貸与していない従業員を割り出したいといったケースですので、①と②のリストの従業員IDを比較して、重複がない「鈴木」さんと「高橋」さんだけが格納されたリストが作れれば良いというイメージです。

:::note info
例のソースコードでは、Employeeクラスと<span>PcAssetクラスに</span><span>LombokというJava特有の冗長なコードを簡潔にしてくれるオープンソースのライブラリを使った場合を想定して書いています。
:::

Lombokについてはこちらを参照してください

https://qiita.com/toranoko92114/items/05cf2dfeab3f2a46ae56

## Java8が出るまでの実装例

StreamAPIが出る以前に全章の要件を実現するには、例えば①と②のリストを多重ループして、従業員IDの重複チェックをする必要がありました。

```java
// 重複排除した従業員リスト 
List<Employee> diffEmployeeList = new ArrayList<Employee>(); 

// 従業員IDの重複チェック 
employeeloop:for (Employee e : employeeList) { 
    for (PcAsset p : pcAssetList) { 
        if (e.getEmployeeId().equals(p.getEmployeeId())) { 
            // 従業員IDが重複している場合は多重ループをcontinue
            continue employeeloop; 
            } 
        } 
        diffEmployeeList.add(e); 
    }
```

なんだかfor文がネストしていてコードが見づらいですし、リストの件数が増えればループの回数が増え、処理速度的に効率が悪いですよね。

## Java8が出てからの実装例（僕が業務で書いた実装）

冒頭で、「重複排除したいリストを起点としてStream変換し、filterメソッドで条件を指定する」ということを言いました。

起点となるリストは①のほうなので、①をStream変換してfilterメソッドで「キー重複していない」って条件を指定すればいいだけじゃんと思いましたが、ここで②のほうにも１つ工夫を入れます。

それは②のリストから従業員IDを要素に持つ集合を作成することで、filterメソッドでの条件指定時にわかりやすくするというもの。

```java
// ②のリストから従業員IDを要素に持つ集合を作成（③とする） 
var pcAssetEmployeeIdSet = pcAssetList.stream().map(pcAsset::getEmployeeId).collect(Collectors.toSet()); 

// ①と③の従業員IDを比較して差分がある従業員IDだけを抜き出したリストを作成 
var diffEmployeeList = employeeList.stream() 
    .filter(Employee -> !pcAssetEmployeeIdSet.contains(Employee.getEmployeeId())) 　
    .collect(Collectors.toList());
```
このようにStreamAPIの機能を使うことで従来よりスッキリとした可読性の高いコードにすることができました！

StreamAPIの効果は絶大ですね、これからもStreamAPIの便利機能をたくさん覚えて、素早い実装と可読性の高いコードの両立を目指していきたいと思います🙌

みなさんも是非覚えて使いこなしてみてください！