【Java】1対NのListをMapに変換

# はじめに
例えば、DBから親子の2テーブルをjoinして抽出した結果をキー毎に処理をしたいとき。
Listのまま処理してもいいけどキーごとにグルーピングした方が見た目きれいですよね？(個人的には可読性が上がるので好きです。性能的に問題が出た場合は仕方ないですが。)
今回はいったん横並びのListで一括取得した後、Map(キー：親テーブル、値：子テーブル)に変換してみます。

# 前提
## ER
### 部署マスタ
- 部署id
- 部署名

### 社員マスタ
- 社員id
- 社員名
- 部署id(FK)

これを内部結合で取得するとこうなりますね。　↓

```sql:select_all.sql
select *
from 部署マスタ
inner join 社員マスタ using 部署id ;
```

取得結果のイメージ（List<結果>）　↓

| 部署 |社員 |
|:----|:----|
|部署１|社員１|
|部署１|社員２|
|部署２|社員３|
|部署２|社員４|
|部署２|：|

これを部署をキー、社員を値とした『Map<部署, List<社員>>』に変換することが今回の目的です。

# 実装

## 環境
- Java8
- Spring boot
- Lombok

## 作ったもの
- DemoService：メイン処理
- DbMapper：実際はMybatisなどのORマッパー。
- DbMapperImpl：テスト用に実装したDbMapper。
- Result：select_all.sqlの結果を格納する
- Department：部署テーブル
- Employee：社員テーブル

## Serviceクラス
一応、**Java7**バージョンのlist2Map7メソッドと、**Java8(StreamAPI)**のlist2Map8メソッド２つ作成しました。
どちらも結果は同じはず。
やっぱりStreamAPIでの実装はコード量も少なく見やすい。
ビジネスロジックと関係ない実装はあまりしたくないですね。
頑張ればlist2Map8ももっと見やすくなるかも？でもServiceクラス以外に可読性の低いコードが増えそうですね。

```java:DemoService.java
package com.example.demo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class DemoService {

	@Autowired
	DbMapper mapper;

	public void execute(String[] args) {
		log.info("### START ###");
		// select from DB
		List<Result> list = mapper.select();
		// 抽出した中身
		list.forEach(System.out::println);
		// List ⇒ LinkedHashMap
		Map<Department, List<Employee>> map = list2Map8(list);
		System.out.println();
		// 変換結果を出力
		for (Map.Entry<Department, List<Employee>> entry : map.entrySet()) {
			System.out.println("Key：" + entry.getKey());
			entry.getValue().forEach(e -> System.out.println("    Value：" + e));
		}
		log.info("### END ###");
	}

	/**
	 * List⇒Map変換(java7Ver).
	 * 
	 * @param list
	 * @return
	 */
	private Map<Department, List<Employee>> list2Map7(List<Result> list) {
		if (list.isEmpty()) {
			return Collections.emptyMap();
		}
		Map<Department, List<Employee>> map = new LinkedHashMap<>();
		// 前回のキー
		Department prevDep = null;
		List<Employee> tempList = null;
		for (Result result : list) {
			Department dep = result.getDepartment();
			// キーが変わったら
			if (!dep.equals(prevDep)) {
				// 値リストを初期化
				tempList = new ArrayList<>();
				// 値が初期化された状態のマップに追加(mapに参照をセット)
				map.put(dep, tempList);
				// 今回のキーを前回キーとする
				prevDep = dep;
			}
			// mapを参照しているtempListに値を追加
			tempList.add(result.getEmployee());
		}
		return map;
	}

	/**
	 * List⇒Map変換(java8Ver).
	 * 
	 * @param list
	 * @return
	 */
	private Map<Department, List<Employee>> list2Map8(List<Result> list) {
		// List(値)⇒Map(キー、値)に変換
		Map<Department, List<Employee>> ret = list.stream()
				.collect(Collectors.groupingBy(Result::getDepartment,
				LinkedHashMap::new, Collectors.mapping(Result::getEmployee, Collectors.toList())));
		return ret;
	}
}
```

取得順を保持するため**LinkedHashMap**を使用しています。
HashMapでは保持されません。
(コメントいただいて、変換メソッド改良しました。ありがとうございます！)

## DbMapperインタフェース
実際はMybatisとかORマッパーを使う感じ。

```java:DbMapper.java
package com.example.demo;

import java.util.List;
import org.springframework.stereotype.Component;

@Component
public interface DbMapper {
	List<Result> select();
}
```

## DbMapperImplクラス
デバッグ用データを返す。

```java:DbMapperImpl.java
package com.example.demo;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Component;

@Component
public class DbMapperImpl implements DbMapper {
	@Override
	public List<Result> select() {
		List<Result> list = new ArrayList<>();
		list.add(getResult(1, 101));
		list.add(getResult(1, 102));
		list.add(getResult(2, 203));
		list.add(getResult(3, 304));
		list.add(getResult(3, 305));
		list.add(getResult(3, 306));
		return list;
	}

	private Result getResult(int did, int eid) {
		Department department = new Department();
		department.setDepartmentId(did);
		department.setDepartmentName("システム" + did + "課");

		Employee employee = new Employee();
		employee.setEmployeeId(eid);
		employee.setName("山田 " + eid + "郎");
		employee.setDepartmentId(department.getDepartmentId());

		Result result = new Result(department, employee);
		return result;
	}
}
```

## Resultクラス
DBからのSELECT結果がまず格納されるオブジェクト。

```java:Result.java
package com.example.demo;

import lombok.Data;

@Data
public class Result {
	private Department department;
	private Employee employee;

	public Result() {
	}

	public Result(Department department, Employee employee) {
		this.department = department;
		this.employee = employee;
	}
}
```

## Departmentクラス
部署マスタ。
Lombokの＠Dataでhashcodeとequalsをオーバーライドしています。社員マスタも同様。
状況によりますが実際にこれがテーブルの1レコードを表す場合、オーバーライドするフィールドはPKだけでいいですね。その場合は＠Dataは使用せずに自前で作成。LinkedHashMap(HashMap)使うのでオーバーライドは必須。

```java:Department.java
package com.example.demo;

import lombok.Data;

@Data
public class Department {
	private Integer departmentId;
	private String departmentName;
}
```


## Employeeクラス
社員マスタ。

```java:Employee.java
package com.example.demo;

import lombok.Data;

@Data
public class Employee {
	private Integer employeeId;
	private String name;
	private Integer departmentId;
}
```

# 実行結果
ちゃんとキー(部署)ごとのマップに変換されていますね。

```log:実行結果
Result(department=Department(departmentId=1, departmentName=システム1課), employee=Employee(employeeId=101, name=山田 101郎, departmentId=1))
Result(department=Department(departmentId=1, departmentName=システム1課), employee=Employee(employeeId=102, name=山田 102郎, departmentId=1))
Result(department=Department(departmentId=2, departmentName=システム2課), employee=Employee(employeeId=203, name=山田 203郎, departmentId=2))
Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=304, name=山田 304郎, departmentId=3))
Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=305, name=山田 305郎, departmentId=3))
Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=306, name=山田 306郎, departmentId=3))

Key：Department(departmentId=1, departmentName=システム1課)
    Value：Employee(employeeId=101, name=山田 101郎, departmentId=1)
    Value：Employee(employeeId=102, name=山田 102郎, departmentId=1)
Key：Department(departmentId=2, departmentName=システム2課)
    Value：Employee(employeeId=203, name=山田 203郎, departmentId=2)
Key：Department(departmentId=3, departmentName=システム3課)
    Value：Employee(employeeId=304, name=山田 304郎, departmentId=3)
    Value：Employee(employeeId=305, name=山田 305郎, departmentId=3)
    Value：Employee(employeeId=306, name=山田 306郎, departmentId=3)
```

# あとがき
Lombok便利ですよね。